<!DOCTYPE html>
<html lang="en-US" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>分布式十二问⭐ | VitePress</title>
    <meta name="description" content="A VitePress site">
    <link rel="preload stylesheet" href="/notebook/assets/style.3dbfd0c2.css" as="style">
    
    <script type="module" src="/notebook/assets/app.8aaa4cbe.js"></script>
    <link rel="preload" href="/notebook/assets/inter-roman-latin.2ed14f66.woff2" as="font" type="font/woff2" crossorigin="">
    <link rel="modulepreload" href="/notebook/assets/chunks/framework.1336c4e5.js">
    <link rel="modulepreload" href="/notebook/assets/chunks/theme.20cddc0c.js">
    <link rel="modulepreload" href="/notebook/assets/三高_分布式.md.db1b8a1b.lean.js">
    <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-255ec12d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-ae3e3f51></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-ae3e3f51> Skip to content </a><!--]--><!----><header class="VPNav" data-v-255ec12d data-v-7e5bc4a5><div class="VPNavBar has-sidebar" data-v-7e5bc4a5 data-v-0937f67c><div class="container" data-v-0937f67c><div class="title" data-v-0937f67c><div class="VPNavBarTitle has-sidebar" data-v-0937f67c data-v-86d1bed8><a class="title" href="/notebook/" data-v-86d1bed8><!--[--><!--]--><!--[--><img class="VPImage logo" src="/notebook/Vue.png" alt data-v-8426fc1a><!--]--><!--[-->任硕的文档<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-0937f67c><div class="curtain" data-v-0937f67c></div><div class="content-body" data-v-0937f67c><!--[--><!--]--><div class="VPNavBarSearch search" style="--vp-meta-key:&#39;Meta&#39;;" data-v-0937f67c><!--[--><!----><div id="docsearch"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg class="DocSearch-Search-Icon" width="20" height="20" viewBox="0 0 20 20" aria-label="search icon"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-0937f67c data-v-7f418b0f><span id="main-nav-aria-label" class="visually-hidden" data-v-7f418b0f>Main Navigation</span><!--[--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>Java学前端</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/HTML+JS.html" data-v-2f2cfafc><!--[-->HTML+JS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/CSS.html" data-v-2f2cfafc><!--[-->CSS<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue2+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue2+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/Vue3+%E7%BB%84%E4%BB%B6.html" data-v-2f2cfafc><!--[-->Vue3+组件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/Java%E5%AD%A6%E5%89%8D%E7%AB%AF/React.html" data-v-2f2cfafc><!--[-->React<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>软件测试</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->测试基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95.html" data-v-2f2cfafc><!--[-->压力测试<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>多线程</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-2f2cfafc><!--[-->基础篇<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E5%B9%B6%E5%8F%91%20&amp;%20%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E5%B9%B6%E5%8F%91%E5%AE%8C%E5%96%84.html" data-v-2f2cfafc><!--[-->进阶篇<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>开发工具</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/Chrome.html" data-v-2f2cfafc><!--[-->Chrome<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E5%9F%BA%E7%A1%80.html" data-v-2f2cfafc><!--[-->IDEA基础<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/IDEA%E6%8F%92%E4%BB%B6.html" data-v-2f2cfafc><!--[-->IDEA插件<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/IDEA/VS%20Code.html" data-v-2f2cfafc><!--[-->VS Code<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup" data-v-7f418b0f data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-a7b5672a><span class="text" data-v-a7b5672a><!----><span data-v-a7b5672a>消息中间件</span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-a7b5672a><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-69e747b5><!----><!--[--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ.html" data-v-2f2cfafc><!--[-->RabbitMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/RocketMQ.html" data-v-2f2cfafc><!--[-->RocketMQ<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka.html" data-v-2f2cfafc><!--[-->Kafka<!--]--></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-69e747b5 data-v-2f2cfafc><a class="VPLink link" href="/notebook/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/Canal.html" data-v-2f2cfafc><!--[-->Canal<!--]--></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-0937f67c data-v-f6a63727><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-f6a63727 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-0937f67c data-v-0394ad82 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-0937f67c data-v-40855f84 data-v-a7b5672a><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-a7b5672a><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-a7b5672a><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-a7b5672a><div class="VPMenu" data-v-a7b5672a data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-40855f84><div class="item appearance" data-v-40855f84><p class="label" data-v-40855f84>Appearance</p><div class="appearance-action" data-v-40855f84><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title="toggle dark mode" aria-checked="false" data-v-40855f84 data-v-82b282f1 data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-82b282f1><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-82b282f1><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><div class="group" data-v-40855f84><div class="item social-links" data-v-40855f84><div class="VPSocialLinks social-links-list" data-v-40855f84 data-v-7bc22406><!--[--><a class="VPSocialLink no-icon" href="https://github.com/renshuo123/renshuo123.github.io" aria-label="github" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a><a class="VPSocialLink no-icon" href="#" aria-label="twitter" target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a><a class="VPSocialLink no-icon" href="https://github.com/" aria-label target="_blank" rel="noopener" data-v-7bc22406 data-v-f80f8133><svg t="1676028692954" class="icon" ...</path></svg></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-0937f67c data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav reached-top" data-v-255ec12d data-v-5cfd5582><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-5cfd5582><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-5cfd5582><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-5cfd5582>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-5cfd5582 data-v-18201f51><button data-v-18201f51>Return to top</button><!----></div></div><aside class="VPSidebar" data-v-255ec12d data-v-845b8fc6><div class="curtain" data-v-845b8fc6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-845b8fc6><span class="visually-hidden" id="sidebar-aria-label" data-v-845b8fc6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Java</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%9B%86%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java集合</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Java/Java%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Java高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Linux</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Linux%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Linux新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/Shell.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Shell脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实用脚本</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Linux/%E8%BD%AF%E4%BB%B6%E9%83%A8%E7%BD%B2.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>软件部署</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Nginx</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E5%AE%9E%E6%88%98%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>实战篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Nginx/%E9%9D%A2%E8%AF%95%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>面试篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SSM</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Maven.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Maven</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/Spring.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Spring</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringMVC.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringMVC</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/SSM/SpringBatch.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringBatch</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringBoot</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E5%BA%94%E7%94%A8%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>应用篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E6%96%B0%E7%89%B9%E6%80%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>新特性</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/3%E3%80%81SpringBoot/%E8%BF%90%E7%BB%B4&amp;%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>运维&原理</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringCloud</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringCloud</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/Sentinel.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Sentinel</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>SpringSecurity</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/SpringSecurity/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>SpringSecurity高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Mybatis & MybatisPlus</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/Mybatis.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Mybatis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/MybatisPlus.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MybatisPlus</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/Mybatis&amp;MybatisPlus/JPA.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JPA</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>Git & ChatGPT</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Git.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Git</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Github.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Github</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/ChatGPT.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ChatGPT</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Jenkins.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Jenkins</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/5%E3%80%81%E8%BF%90%E7%BB%B4/Netty.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Netty</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>数据库</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MySQL</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%AE%BE%E8%AE%A1.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL设计</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/MySQL%E6%A0%B8%E5%BF%83/%E8%BF%90%E7%BB%B4.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MySQL运维</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分库分表</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Redis</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%8E%9F%E7%90%86.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis原理</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/Redis%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Redis实战</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Redis/%E6%9C%AC%E5%9C%B0%E7%BC%93%E5%AD%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>本地缓存</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>MongoDB</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/MongoDB/%E6%95%B4%E5%90%88.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>MongoDB进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>ElasticSearch</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/1%E3%80%81ES%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/ElasticSearch/3%E3%80%81ES%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES高级</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/influxdb.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>InfluxDB</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Neo4j</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible has-active" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>高并发 & 秒杀 & 分布式</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E5%88%86%E5%B8%83%E5%BC%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式理论</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>分布式锁</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E7%A7%92%E6%9D%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>秒杀</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%8F%AF%E7%94%A8.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高可用</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%B8%89%E9%AB%98/%E9%AB%98%E5%B9%B6%E5%8F%91.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高并发</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>云原生</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/Docker.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Docker</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E4%BA%91%E5%8E%9F%E7%94%9F/K8S.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>K8S</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>可视化 & 监控</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E7%9B%91%E6%8E%A7%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>监控进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%A7%E5%B1%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>可视化大屏</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E5%8F%AF%E8%A7%86%E5%8C%96%20&amp;%20%E7%9B%91%E6%8E%A7/Zabbix.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Zabbix</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>学前端</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>HTML+CSS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/HTML%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>HTML基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/CSS%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>CSS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/1%E3%80%81HTML+CSS/%E7%BD%91%E9%A1%B5%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>网页进阶</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>JS+TS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/JS%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>JS进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/ES6%20%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>ES6进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/2%E3%80%81JS+TS/TypeScript.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>TS基础</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>NodeJS</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Node进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/4%E3%80%81Node/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>Vue</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E8%BF%9B%E9%98%B6.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3进阶</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E9%AB%98%E7%BA%A7.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3高级</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue3/Vue3%E6%96%B0%E8%AF%AD%E6%B3%95.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>Vue3新语法</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/3%E3%80%81Vue/Vue2/Vue2%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小程序</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序基础</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BC%98%E5%8C%96.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>小程序优化</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/uniapp.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>uniapp</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/1%E3%80%81%E5%AD%A6%E5%89%8D%E7%AB%AF/5%E3%80%81%E5%B0%8F%E7%A8%8B%E5%BA%8F/%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目实战</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>计算机基础</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>数据结构</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>操作系统</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>设计模式</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>计算机网络</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/UML.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>UML</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95/LeetCode.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>LeetCode</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0 collapsible collapsed" data-v-845b8fc6 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h2 class="text" data-v-9b797284>项目实战</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>云尚办公</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E4%BA%91%E5%B0%9A%E5%8A%9E%E5%85%AC/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>小兔鲜</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%871.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇1</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E5%B0%8F%E5%85%94%E9%B2%9C/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>地图</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>苍穹外卖</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E8%8B%8D%E7%A9%B9%E5%A4%96%E5%8D%96/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1 collapsible collapsed" data-v-9b797284 data-v-9b797284><div class="item" role="button" tabindex="0" data-v-9b797284><div class="indicator" data-v-9b797284></div><h3 class="text" data-v-9b797284>黑马头条</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-9b797284><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="caret-icon" data-v-9b797284><path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"></path></svg></div></div><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E5%9F%BA%E7%A1%80%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>基础篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E8%BF%9B%E9%98%B6%E7%AF%872.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>进阶篇2</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%BB%91%E9%A9%AC%E5%A4%B4%E6%9D%A1/%E9%AB%98%E7%BA%A7%E7%AF%87.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>高级篇</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E6%94%AF%E4%BB%98.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>支付</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>项目推荐</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="group" data-v-845b8fc6><section class="VPSidebarItem level-0" data-v-845b8fc6 data-v-9b797284><!----><div class="items" data-v-9b797284><!--[--><div class="VPSidebarItem level-1 is-link" data-v-9b797284 data-v-9b797284><div class="item" data-v-9b797284><div class="indicator" data-v-9b797284></div><a class="VPLink link link" href="/notebook/team.html" data-v-9b797284><!--[--><p class="text" data-v-9b797284>团队成员</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-255ec12d data-v-669faec9><div class="VPDoc has-sidebar has-aside" data-v-669faec9 data-v-6b87e69f><!--[--><!--]--><div class="container" data-v-6b87e69f><div class="aside" data-v-6b87e69f><div class="aside-curtain" data-v-6b87e69f></div><div class="aside-container" data-v-6b87e69f><div class="aside-content" data-v-6b87e69f><div class="VPDocAside" data-v-6b87e69f data-v-3f215769><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-3f215769 data-v-ff0f39c8><div class="content" data-v-ff0f39c8><div class="outline-marker" data-v-ff0f39c8></div><div class="outline-title" data-v-ff0f39c8>On this page</div><nav aria-labelledby="doc-outline-aria-label" data-v-ff0f39c8><span class="visually-hidden" id="doc-outline-aria-label" data-v-ff0f39c8> Table of Contents for current page </span><ul class="root" data-v-ff0f39c8 data-v-d0ee3533><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-6b87e69f><div class="content-container" data-v-6b87e69f><!--[--><!--]--><!----><main class="main" data-v-6b87e69f><div style="position:relative;" class="vp-doc _notebook_%E4%B8%89%E9%AB%98_%E5%88%86%E5%B8%83%E5%BC%8F" data-v-6b87e69f><div><h1 id="分布式十二问⭐" tabindex="-1">分布式十二问⭐ <a class="header-anchor" href="#分布式十二问⭐" aria-label="Permalink to &quot;分布式十二问⭐&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=MzAwODc2ODgzMw==&amp;mid=2247529348&amp;idx=1&amp;sn=dc64a759a6f670725355005b30757cc2&amp;chksm=9b6befaaac1c66bc93af21deb1d461781a5e09148545c5e216783498b1e047ec2414c0441241&amp;mpshare=1&amp;scene=23&amp;srcid=0220QGUO4vcGEJKR0zHzR1O9&amp;sharer_sharetime=1676906139434&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">面渣逆袭：分布式十二问！万字图文详解！</a></p><h2 id="分布式理论" tabindex="-1">分布式理论 <a class="header-anchor" href="#分布式理论" aria-label="Permalink to &quot;分布式理论&quot;">​</a></h2><h3 id="_1-说说cap原则" tabindex="-1">1. 说说CAP原则？ <a class="header-anchor" href="#_1-说说cap原则" aria-label="Permalink to &quot;1. 说说CAP原则？&quot;">​</a></h3><p>CAP原则又称CAP定理，指的是在一个分布式系统中，Consistency（一致性）、 Availability（可用性）、Partition tolerance（分区容错性）这3个基本需求，最多只能同时满足其中的2个。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211742501.png" alt="image-20230221174232416" style="zoom:33%;"><table><thead><tr><th style="text-align:left;">选项</th><th style="text-align:left;">描述</th></tr></thead><tbody><tr><td style="text-align:left;">Consistency（一致性）</td><td style="text-align:left;">指数据在多个副本之间能够保持一致的特性（严格的一致性）</td></tr><tr><td style="text-align:left;">Availability（可用性）</td><td style="text-align:left;">指系统提供的服务必须一直处于可用的状态，每次请求都能获取到非错的响应（不保证获取的数据为最新数据）</td></tr><tr><td style="text-align:left;">Partition tolerance（分区容错性）</td><td style="text-align:left;">分布式系统在遇到任何网络分区故障的时候，仍然能够对外提供满足一致性和可用性的服务，除非整个网络环境都发生了故障</td></tr></tbody></table><h3 id="_2-为什么cap不可兼得呢" tabindex="-1">2. 为什么CAP不可兼得呢？ <a class="header-anchor" href="#_2-为什么cap不可兼得呢" aria-label="Permalink to &quot;2. 为什么CAP不可兼得呢？&quot;">​</a></h3><p>首先对于分布式系统，分区是必然存在的，所谓分区指的是分布式系统可能出现的字区域网络不通，成为孤立区域的的情况。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211742315.png" alt="image-20230221174257255" style="zoom:50%;"><p>那么分区容错性（<strong>P</strong>）就必须要满足，因为如果要牺牲分区容错性，就得把服务和资源放到一个机器，或者一个“同生共死”的集群，那就违背了分布式的初衷。</p><p>那么满足分区容错的基础上，能不能同时满足<code>一致性</code>和<code>可用性</code>？</p><p>假如现在有两个分区<code>N1</code>和<code>N2</code>，N1和N2分别有不同的分区存储D1和D2，以及不同的服务S1和S2。</p><ul><li>在满足<code>一致性</code> 的时候，N1和N2的数据要求值一样的，D1=D2。</li><li>在满足<code>可用性</code>的时候，无论访问N1还是N2，都能获取及时的响应。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211743053.png" alt="image-20230221174316939" style="zoom:50%;"><p>分区的服务</p><p>假如现在有这样的场景：</p><ul><li>用户访问了N1，修改了D1的数据。</li><li>用户再次访问，请求落在了N2。此时D1和D2的数据不一致。</li></ul><p>接下来：</p><ul><li>保证<code>一致性</code>：此时D1和D2数据不一致，要保证一致性就不能返回不一致的数据，<code>可用性</code>无法保证。</li><li>保证<code>可用性</code>：立即响应，可用性得到了保证，但是此时响应的数据和D1不一致，<code>一致性</code>无法保证。</li></ul><p>所以，可以看出，分区容错的前提下，<code>一致性</code>和<code>可用性</code>是矛盾的。</p><h3 id="_3-cap对应的模型和应用" tabindex="-1">3. CAP对应的模型和应用？ <a class="header-anchor" href="#_3-cap对应的模型和应用" aria-label="Permalink to &quot;3. CAP对应的模型和应用？&quot;">​</a></h3><p><strong>CA without P</strong></p><p>理论上放弃P（分区容错性），则C（强一致性）和A（可用性）是可以保证的。实际上分区是不可避免的，严格上CA指的是允许分区后各子系统依然保持CA。</p><p>CA模型的常见应用：</p><ul><li>集群数据库</li><li>xFS文件系统</li></ul><p><strong>CP without A</strong></p><p>放弃A（可用），相当于每个请求都需要在Server之间强一致，而P（分区）会导致同步时间无限延长，如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。</p><p>CP模型的常见应用：</p><ul><li>分布式数据库</li><li>分布式锁</li></ul><p><strong>AP wihtout C</strong></p><p>要高可用并允许分区，则需放弃一致性。一旦分区发生，节点之间可能会失去联系，为了高可用，每个节点只能用本地数据提供服务，而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。</p><p>AP模型常见应用：</p><ul><li>Web缓存</li><li>DNS</li></ul><p>举个大家更熟悉的例子，像我们熟悉的注册中心<code>ZooKeeper</code>、<code>Eureka</code>、<code>Nacos</code>中：</p><ul><li>ZooKeeper 保证的是 CP</li><li>Eureka 保证的则是 AP</li><li>Nacos 不仅支持 CP 也支持 AP</li></ul><h3 id="_4-base理论了解吗" tabindex="-1">4. BASE理论了解吗？ <a class="header-anchor" href="#_4-base理论了解吗" aria-label="Permalink to &quot;4. BASE理论了解吗？&quot;">​</a></h3><p>BASE（Basically Available、Soft state、Eventual consistency）是基于CAP理论逐步演化而来的，核心思想是即便不能达到强一致性（Strong consistency），也可以根据应用特点采用适当的方式来达到最终一致性（Eventual consistency）的效果。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211743499.png" alt="image-20230221174335435" style="zoom:67%;"><p>BASE的主要含义：</p><ul><li><strong>Basically Available（基本可用）</strong></li></ul><p>什么是基本可用呢？假设系统出现了不可预知的故障，但还是能用，只是相比较正常的系统而言，可能会有响应时间上的损失，或者功能上的降级。</p><ul><li><strong>Soft State（软状态）</strong></li></ul><p>什么是硬状态呢？要求多个节点的数据副本都是一致的，这是一种“硬状态”。</p><p>软状态也称为弱状态，相比较硬状态而言，允许系统中的数据存在中间状态，并认为该状态不影响系统的整体可用性，即允许系统在多个不同节点的数据副本存在数据延时。</p><ul><li><strong>Eventually Consistent（最终一致性）</strong></li></ul><p>上面说了软状态，但是不应该一直都是软状态。在一定时间后，应该到达一个最终的状态，保证所有副本保持数据一致性，从而达到数据的最终一致性。这个时间取决于网络延时、系统负载、数据复制方案设计等等因素。</p><h2 id="分布式锁" tabindex="-1">分布式锁 <a class="header-anchor" href="#分布式锁" aria-label="Permalink to &quot;分布式锁&quot;">​</a></h2><p>单体时代，可以直接用本地锁来实现对竞争资源的加锁，分布式环境下就要用到分布式锁了。</p><h3 id="_1-有哪些分布式锁的实现方案呢" tabindex="-1">1. 有哪些分布式锁的实现方案呢？ <a class="header-anchor" href="#_1-有哪些分布式锁的实现方案呢" aria-label="Permalink to &quot;1. 有哪些分布式锁的实现方案呢？&quot;">​</a></h3><p>常见的分布式锁实现方案有三种：<code>MySQL分布式锁</code>、<code>ZooKepper分布式锁</code>、<code>Redis分布式锁</code>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211744011.png" alt="image-20230221174408938" style="zoom:67%;"><p>分布式锁</p><h3 id="_2-mysql分布式锁如何实现呢" tabindex="-1">2. MySQL分布式锁如何实现呢？ <a class="header-anchor" href="#_2-mysql分布式锁如何实现呢" aria-label="Permalink to &quot;2. MySQL分布式锁如何实现呢？&quot;">​</a></h3><p>用数据库实现分布式锁比较简单，就是创建一张锁表，数据库对字段作唯一性约束。</p><p>加锁的时候，在锁表中增加一条记录即可；释放锁的时候删除记录就行。</p><p>如果有并发请求同时提交到数据库，数据库会保证只有一个请求能够得到锁。</p><p>这种属于数据库 IO 操作，效率不高，而且频繁操作会增大数据库的开销，因此这种方式在高并发、高性能的场景中用的不多。</p><h3 id="_3-zookeeper如何实现分布式锁" tabindex="-1">3. ZooKeeper如何实现分布式锁？ <a class="header-anchor" href="#_3-zookeeper如何实现分布式锁" aria-label="Permalink to &quot;3. ZooKeeper如何实现分布式锁？&quot;">​</a></h3><p>ZooKeeper也是常见分布式锁实现方法。</p><p>ZooKeeper的数据节点和文件目录类似，例如有一个lock节点，在此节点下建立子节点是可以保证先后顺序的，即便是两个进程同时申请新建节点，也会按照先后顺序建立两个节点。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211744604.png" alt="image-20230221174423531" style="zoom:67%;"><p>ZooKeeper如何实现分布式锁</p><p>所以我们可以用此特性实现分布式锁。以某个资源为目录，然后这个目录下面的节点就是我们需要获取锁的客户端，每个服务在目录下创建节点，如果它的节点，序号在目录下最小，那么就获取到锁，否则等待。释放锁，就是删除服务创建的节点。</p><p>ZK实际上是一个比较重的分布式组件，实际上应用没那么多了，所以用ZK实现分布式锁，其实相对也比较少。</p><h3 id="_4-redis怎么实现分布式锁" tabindex="-1">4. Redis怎么实现分布式锁？ <a class="header-anchor" href="#_4-redis怎么实现分布式锁" aria-label="Permalink to &quot;4. Redis怎么实现分布式锁？&quot;">​</a></h3><p>Redis实现分布式锁，是当前应用最广泛的分布式锁实现方式。</p><p>Redis执行命令是单线程的，Redis实现分布式锁就是利用这个特性。</p><p>实现分布式锁最简单的一个命令：setNx(set if not exist)，如果不存在则更新：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">setNx resourceName value</span></span></code></pre></div><p>加锁了之后如果机器宕机，那我这个锁就无法释放，所以需要加入过期时间，而且过期时间需要和setNx同一个原子操作，在Redis2.8之前需要用lua脚本，但是redis2.8之后redis支持nx和ex操作是同一原子操作。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">set resourceName value ex 5 nx</span></span></code></pre></div><ul><li><strong>Redission</strong></li></ul><p>当然，一般生产中都是使用Redission客户端，非常良好地封装了分布式锁的api，而且支持RedLock。</p><h2 id="分布式事务" tabindex="-1">分布式事务 <a class="header-anchor" href="#分布式事务" aria-label="Permalink to &quot;分布式事务&quot;">​</a></h2><h3 id="_1-什么是分布式事务" tabindex="-1">1. 什么是分布式事务? <a class="header-anchor" href="#_1-什么是分布式事务" aria-label="Permalink to &quot;1. 什么是分布式事务?&quot;">​</a></h3><p>分布式事务是相对本地事务而言的，对于本地事务，利用数据库本身的事务机制，就可以保证事务的ACID特性。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211744086.png" alt="image-20230221174447012" style="zoom:50%;"><p>ACID</p><p>而在分布式环境下，会涉及到多个数据库。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211746180.png" alt="image-20230221174636111" style="zoom:50%;"><p>多数据库</p><p>分布式事务其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。</p><p>分布式事务处理的关键是：</p><ol><li>需要记录事务在任何节点所做的所有动作；</li><li>事务进行的所有操作要么全部提交，要么全部回滚。</li></ol><h3 id="_2-分布式事务有哪些常见的实现方案" tabindex="-1">2.分布式事务有哪些常见的实现方案？ <a class="header-anchor" href="#_2-分布式事务有哪些常见的实现方案" aria-label="Permalink to &quot;2.分布式事务有哪些常见的实现方案？&quot;">​</a></h3><p>分布式常见的实现方案有 <strong>2PC</strong>、<strong>3PC</strong>、<strong>TCC</strong>、<strong>本地消息表</strong>、<strong>MQ消息事务</strong>、<strong>最大努力通知</strong>、<strong>SAGA事务</strong> 等等。</p><h3 id="_3-说说2pc两阶段提交" tabindex="-1">3. 说说2PC两阶段提交？ <a class="header-anchor" href="#_3-说说2pc两阶段提交" aria-label="Permalink to &quot;3. 说说2PC两阶段提交？&quot;">​</a></h3><p>说到2PC，就不得先说分布式事务中的 XA 协议。</p><p>在这个协议里，有三个角色：</p><ul><li><strong>AP（Application）</strong>：应用系统（服务）</li><li><strong>TM（Transaction Manager）</strong>：事务管理器（全局事务管理）</li><li><strong>RM（Resource Manager）</strong>：资源管理器（数据库）</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211746723.png" alt="image-20230221174650664" style="zoom:67%;"><p>XA协议</p><p>XA协议采用<strong>两阶段提交</strong>方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通信的标准接口。</p><p>两阶段提交的思路可以概括为：参与者将操作成败通知协调者，再由协调者根据所有参与者的反馈情况决定各参与者是否要提交操作还是回滚操作。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211747859.png" alt="image-20230221174702792" style="zoom:67%;"><p>2PC</p><ul><li>准备阶段：事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作，并反映是否可以提交</li><li>提交阶段：事务协调器要求每个数据库提交数据，或者回滚数据。</li></ul><p>优点：尽量保证了数据的强一致，实现成本较低，在各大主流数据库都有自己实现，对于MySQL是从5.5开始支持。</p><p>缺点:</p><ul><li>单点问题：事务管理器在整个流程中扮演的角色很关键，如果其宕机，比如在第一阶段已经完成，在第二阶段正准备提交的时候事务管理器宕机，资源管理器就会一直阻塞，导致数据库无法使用。</li><li>同步阻塞：在准备就绪之后，资源管理器中的资源一直处于阻塞，直到提交完成，释放资源。</li><li>数据不一致：两阶段提交协议虽然为分布式数据强一致性所设计，但仍然存在数据不一致性的可能，比如在第二阶段中，假设协调者发出了事务commit的通知，但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作，其余的参与者则因为没有收到通知一直处于阻塞状态，这时候就产生了数据的不一致性。</li></ul><h3 id="_4-3pc-三阶段提交-了解吗" tabindex="-1">4. 3PC（三阶段提交）了解吗？ <a class="header-anchor" href="#_4-3pc-三阶段提交-了解吗" aria-label="Permalink to &quot;4. 3PC（三阶段提交）了解吗？&quot;">​</a></h3><p>三阶段提交（<code>3PC</code>）是二阶段提交（<code>2PC</code>）的一种改进版本 ，为解决两阶段提交协议的单点故障和同步阻塞问题。</p><p>三阶段提交有这么三个阶段：<code>CanCommit</code>，<code>PreCommit</code>，<code>DoCommit</code>三个阶段</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211747163.png" alt="image-20230221174717064" style="zoom:80%;"><p>3PC</p><ul><li><p><strong>CanCommit</strong>：准备阶段。协调者向参与者发送commit请求，参与者如果可以提交就返回Yes响应，否则返回No响应。</p></li><li><p><strong>PreCommit</strong>：预提交阶段。协调者根据参与者在<strong>准备阶段</strong>的响应判断是否执行事务还是中断事务，参与者执行完操作之后返回ACK响应，同时开始等待最终指令。</p></li><li><p><strong>DoCommit</strong>：提交阶段。协调者根据参与者在<strong>准备阶段</strong>的响应判断是否执行事务还是中断事务：</p></li><li><ul><li>如果所有参与者都返回正确的<code>ACK</code>响应，则提交事务</li><li>如果参与者有一个或多个参与者收到错误的<code>ACK</code>响应或者超时，则中断事务</li><li>如果参与者无法及时接收到来自协调者的提交或者中断事务请求时，在等待超时之后，会继续进行事务提交</li></ul></li></ul><p>可以看出，三阶段提交解决的只是两阶段提交中<strong>单体故障</strong>和<strong>同步阻塞</strong>的问题，因为加入了超时机制，这里的超时的机制作用于 <strong>预提交阶段</strong> 和 <strong>提交阶段</strong>。如果等待 <strong>预提交请求</strong> 超时，参与者直接回到准备阶段之前。如果等到<strong>提交请求</strong>超时，那参与者就会提交事务了。</p><p><strong>无论是2PC还是3PC都不能保证分布式系统中的数据100%一致</strong>。</p><h3 id="_5-tcc了解吗" tabindex="-1">5. TCC了解吗？ <a class="header-anchor" href="#_5-tcc了解吗" aria-label="Permalink to &quot;5. TCC了解吗？&quot;">​</a></h3><p><strong>TCC（Try Confirm Cancel）</strong> ，是两阶段提交的一个变种，针对每个操作，都需要有一个其对应的确认和取消操作，当操作成功时调用确认操作，当操作失败时调用取消操作，类似于二阶段提交，只不过是这里的提交和回滚是针对业务上的，所以基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211747244.png" alt="image-20230221174737169" style="zoom:67%;"><p>TCC下单减库存</p><ul><li><strong>Try</strong>：尝试待执行的业务。订单系统将当前订单状态设置为支付中，库存系统校验当前剩余库存数量是否大于1，然后将可用库存数量设置为库存剩余数量-1，。</li><li><strong>Confirm</strong>：确认执行业务，如果Try阶段执行成功，接着执行Confirm 阶段，将订单状态修改为支付成功，库存剩余数量修改为可用库存数量。</li><li><strong>Cancel</strong>：取消待执行的业务，如果Try阶段执行失败，执行Cancel 阶段，将订单状态修改为支付失败，可用库存数量修改为库存剩余数量。</li></ul><p><strong>TCC</strong> 是业务层面的分布式事务，保证最终一致性，不会一直持有资源的锁。</p><ul><li><strong>优点：</strong> 把数据库层的二阶段提交交给应用层来实现，规避了数据库的 2PC 性能低下问题</li><li><strong>缺点</strong>：TCC 的 Try、Confirm 和 Cancel 操作功能需业务提供，开发成本高。TCC 对业务的侵入较大和业务紧耦合，需要根据特定的场景和业务逻辑来设计相应的操作</li></ul><h3 id="_6-本地消息表了解吗" tabindex="-1">6. 本地消息表了解吗？ <a class="header-anchor" href="#_6-本地消息表了解吗" aria-label="Permalink to &quot;6. 本地消息表了解吗？&quot;">​</a></h3><p>本地消息表的核心思想是将分布式事务拆分成本地事务进行处理。</p><p>例如，可以在订单库新增一个消息表，将新增订单和新增消息放到一个事务里完成，然后通过轮询的方式去查询消息表，将消息推送到MQ，库存服务去消费MQ。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211747843.png" alt="image-20230221174753785" style="zoom:67%;"><p>本地消息表</p><p><strong>执行流程：</strong></p><ol><li>订单服务，添加一条订单和一条消息，在一个事务里提交</li><li>订单服务，使用定时任务轮询查询状态为未同步的消息表，发送到MQ，如果发送失败，就重试发送</li><li>库存服务，接收MQ消息，修改库存表，需要保证幂等操作</li><li>如果修改成功，调用rpc接口修改订单系统消息表的状态为已完成或者直接删除这条消息</li><li>如果修改失败，可以不做处理，等待重试</li></ol><p>订单服务中的消息有可能由于业务问题会一直重复发送，所以为了避免这种情况可以记录一下发送次数，当达到次数限制之后报警，人工接入处理；库存服务需要保证幂等，避免同一条消息被多次消费造成数据不一致。</p><p>本地消息表这种方案实现了最终一致性，需要在业务系统里增加消息表，业务逻辑中多一次插入的DB操作，所以性能会有损耗，而且最终一致性的间隔主要有定时任务的间隔时间决定</p><h3 id="_7-mq消息事务了解吗" tabindex="-1">7. MQ消息事务了解吗？ <a class="header-anchor" href="#_7-mq消息事务了解吗" aria-label="Permalink to &quot;7. MQ消息事务了解吗？&quot;">​</a></h3><p>消息事务的原理是<strong>将两个事务通过消息中间件进行异步解耦</strong>。</p><p>订单服务执行自己的本地事务，并发送MQ消息，库存服务接收消息，执行自己的本地事务，乍一看，好像跟本地消息表的实现方案类似，只是省去 了对本地消息表的操作和轮询发送MQ的操作，但实际上两种方案的实现是不一样的。</p><p>消息事务一定要保证业务操作与消息发送的一致性，如果业务操作成功，这条消息也一定投递成功。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211751165.png" alt="image-20230221175150091" style="zoom:67%;"><p>MQ消息事务</p><p><strong>执行流程：</strong></p><ol><li>发送prepare消息到消息中间件</li><li>发送成功后，执行本地事务</li><li>如果事务执行成功，则commit，消息中间件将消息下发至消费端</li><li>如果事务执行失败，则回滚，消息中间件将这条prepare消息删除</li><li>消费端接收到消息进行消费，如果消费失败，则不断重试</li></ol><p>消息事务依赖于消息中间件的事务消息，例如我们熟悉的RocketMQ就支持事务消息（半消息），也就是只有收到发送方确定才会正常投递的消息。</p><p>这种方案也是实现了最终一致性，对比本地消息表实现方案，不需要再建消息表，对性能的损耗和业务的入侵更小。</p><h3 id="_8-最大努力通知了解吗" tabindex="-1">8. 最大努力通知了解吗？ <a class="header-anchor" href="#_8-最大努力通知了解吗" aria-label="Permalink to &quot;8. 最大努力通知了解吗？&quot;">​</a></h3><p>最大努力通知相比实现会简单一些，适用于一些对最终一致性实时性要求没那么高的业务，比如支付通知，短信通知。</p><p>以支付通知为例，业务系统调用支付平台进行支付，支付平台进行支付，进行操作支付之后支付平台会去同步通知业务系统支付操作是否成功，如果不成功，会一直异步重试，但是会有一个最大通知次数，如果超过这个次数后还是通知失败，就不再通知，业务系统自行调用支付平台提供一个查询接口，供业务系统进行查询支付操作是否成功。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211751945.png" alt="image-20230221175135882" style="zoom:67%;"><p>最大努力通知</p><p><strong>执行流程：</strong></p><ol><li>业务系统调用支付平台支付接口， 并在本地进行记录，支付状态为支付中</li><li>支付平台进行支付操作之后，无论成功还是失败，同步给业务系统一个结果通知</li><li>如果通知一直失败则根据重试规则异步进行重试，达到最大通知次数后，不再通知</li><li>支付平台提供查询订单支付操作结果接口</li><li>业务系统根据一定业务规则去支付平台查询支付结果</li></ol><h3 id="_9-你们用什么-能说一下seata吗" tabindex="-1">9. 你们用什么？能说一下Seata吗？ <a class="header-anchor" href="#_9-你们用什么-能说一下seata吗" aria-label="Permalink to &quot;9. 你们用什么？能说一下Seata吗？&quot;">​</a></h3><p>我们用比较常用的是Seata——自己去实现分布式事务调度还是比较麻烦的。</p><p><strong>Seata</strong> 的设计目标是对业务无侵入，因此它是从业务无侵入的两阶段提交（全局事务）着手，在传统的两阶段上进行改进，他把一个分布式事务理解成一个包含了若干分支事务的全局事务。而全局事务的职责是协调它管理的分支事务达成一致性，要么一起成功提交，要么一起失败回滚。也就是一荣俱荣一损俱损~</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211751770.png" alt="image-20230221175111705" style="zoom:50%;"><p>全局事务和分支事务</p><p><strong>Seata</strong> 中存在这么几种重要角色：</p><ul><li><strong>TC（Transaction Coordinator）</strong>：事务协调者。管理全局的分支事务的状态，用于全局性事务的提交和回滚。</li><li><strong>TM（Transaction Manager）</strong>：事务管理者。用于开启、提交或回滚事务。</li><li><strong>RM（Resource Manager）</strong>：资源管理器。用于分支事务上的资源管理，向 <strong>TC</strong> 注册分支事务，上报分支事务的状态，接收 <strong>TC</strong> 的命令来提交或者回滚分支事务。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211751063.png" alt="image-20230221175059970" style="zoom:50%;"><p>Seata工作流程</p><p>S&#39;eata整体执行流程：</p><ol><li>服务A中的 <strong>TM</strong> 向 <strong>TC</strong> 申请开启一个全局事务，<strong>TC</strong> 就会创建一个全局事务并返回一个唯一的 <strong>XID</strong></li><li>服务A中的 <strong>RM</strong> 向 <strong>TC</strong> 注册分支事务，然后将这个分支事务纳入 <strong>XID</strong> 对应的全局事务管辖中</li><li>服务A开始执行分支事务</li><li>服务A开始远程调用B服务，此时 <strong>XID</strong> 会根据调用链传播</li><li>服务B中的 <strong>RM</strong> 也向 <strong>TC</strong> 注册分支事务，然后将这个分支事务纳入 <strong>XID</strong> 对应的全局事务管辖中</li><li>服务B开始执行分支事务</li><li>全局事务调用处理结束后，<strong>TM</strong> 会根据有误异常情况，向 <strong>TC</strong> 发起全局事务的提交或回滚</li><li><strong>TC</strong> 协调其管辖之下的所有分支事务，决定是提交还是回滚</li></ol><h2 id="分布式一致性算法" tabindex="-1">分布式一致性算法 <a class="header-anchor" href="#分布式一致性算法" aria-label="Permalink to &quot;分布式一致性算法&quot;">​</a></h2><h3 id="_1-分布式算法paxos了解么" tabindex="-1">1. 分布式算法paxos了解么 ？ <a class="header-anchor" href="#_1-分布式算法paxos了解么" aria-label="Permalink to &quot;1. 分布式算法paxos了解么 ？&quot;">​</a></h3><p><code>Paxos</code> 有点类似前面说的 <code>2PC</code>，<code>3PC</code>，但比这两种算法更加完善。在很多多大厂都得到了工程实践，比如阿里的 <code>OceanBase</code> 的 <strong>分布式数据库</strong>， <code>Google</code> 的 <code>chubby</code> <strong>分布式锁</strong> 。</p><h3 id="_2-paxos算法是什么" tabindex="-1">2. Paxos算法是什么？ <a class="header-anchor" href="#_2-paxos算法是什么" aria-label="Permalink to &quot;2. Paxos算法是什么？&quot;">​</a></h3><p><code>Paxos</code> 算法是 <strong>基于消息传递</strong> 且具有 <strong>高效容错性</strong> 的一致性算法，目前公认的解决 <strong>分布式一致性问题</strong> 最有效的算法之一</p><h3 id="_3-paxos算法的工作流程" tabindex="-1">3. Paxos算法的工作流程？ <a class="header-anchor" href="#_3-paxos算法的工作流程" aria-label="Permalink to &quot;3. Paxos算法的工作流程？&quot;">​</a></h3><h4 id="角色" tabindex="-1">角色 <a class="header-anchor" href="#角色" aria-label="Permalink to &quot;角色&quot;">​</a></h4><p>在Paxos中有这么几个角色：</p><ol><li><strong>Proposer（提议者）</strong> : 提议者提出提案，用于投票表决。</li><li><strong>Accecptor（接受者）</strong> : 对提案进行投票，并接受达成共识的提案。</li><li><strong>Learner（学习者）</strong> : 被告知投票的结果，接受达成共识的提案。</li></ol><p>在实际中，一个节点可以同时充当不同角色。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211750268.png" alt="image-20230221175041192" style="zoom:50%;"><p>Paxos的三种角色</p><p>提议者提出提案，提案=编号+value，可以表示为[M,V]，每个提案都有唯一编号，而且编号的大小是趋势递增的。</p><h4 id="算法流程" tabindex="-1">算法流程 <a class="header-anchor" href="#算法流程" aria-label="Permalink to &quot;算法流程&quot;">​</a></h4><p>Paxos算法包含两个阶段，第一阶段<strong>Prepare(准备)</strong>、第二阶段<strong>Accept(接受)</strong>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211750617.png" alt="image-20230221175026543" style="zoom:67%;"><p>Paxos算法流程</p><h5 id="prepare-准备-阶段" tabindex="-1">Prepare(准备)阶段 <a class="header-anchor" href="#prepare-准备-阶段" aria-label="Permalink to &quot;Prepare(准备)阶段&quot;">​</a></h5><ol><li>提议者提议一个新的提案 P[Mn,?]，然后向接受者的某个超过半数的子集成员发送编号为Mn的准备请求</li><li>如果一个接受者收到一个编号为Mn的准备请求，并且编号Mn大于它已经响应的所有准备请求的编号，那么它就会将它已经批准过的最大编号的提案作为响应反馈给提议者，同时该接受者会承诺不会再批准任何编号小于Mn的提案。</li></ol><p>总结一下，接受者在收到提案后，会给与提议者<strong>两个承诺</strong>与<strong>一个应答</strong>：</p><ul><li><p>两个承诺：</p></li><li><ul><li>承诺不会再接受提案号小于或等于 Mn 的 Prepare 请求</li><li>承诺不会再接受提案号小于Mn 的 Accept 请求</li></ul></li><li><p>一个应答：</p></li><li><ul><li>不违背以前作出的承诺的前提下，回复已经通过的提案中提案号最大的那个提案所设定的值和提案号Mmax，如果这个值从来没有被任何提案设定过，则返回空值。如果不满足已经做出的承诺，即收到的提案号并不是决策节点收到过的最大的，那允许直接对此 Prepare 请求不予理会。</li></ul></li></ul><h5 id="accept-接受-阶段" tabindex="-1">Accept(接受)阶段 <a class="header-anchor" href="#accept-接受-阶段" aria-label="Permalink to &quot;Accept(接受)阶段&quot;">​</a></h5><ol><li>如果提议者收到来自半数以上的接受者对于它发出的编号为Mn的准备请求的响应，那么它就会发送一个针对[Mn,Vn]的接受请求给接受者，注意Vn的值就是收到的响应中编号最大的提案的值，如果响应中不包含任何提案，那么它可以随意选定一个值。</li><li>如果接受者收到这个针对[Mn,Vn]提案的接受请求，只要该接受者尚未对编号大于Mn的准备请求做出响应，它就可以通过这个提案。</li></ol><p>当提议者收到了多数接受者的接受应答后，协商结束，共识决议形成，将形成的决议发送给所有学习节点进行学习。</p><p>所以Paxos算法的整体详细流程如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211750414.png" alt="image-20230221175008328" style="zoom:67%;"><p>Paxos详细流程</p><p>算法的流程模拟，可以查看参考[13]。</p><h3 id="_4-paxos算法有什么缺点吗-怎么优化" tabindex="-1">4. Paxos算法有什么缺点吗？怎么优化？ <a class="header-anchor" href="#_4-paxos算法有什么缺点吗-怎么优化" aria-label="Permalink to &quot;4. Paxos算法有什么缺点吗？怎么优化？&quot;">​</a></h3><p>前面描述的可以称之为Basic Paxos 算法，在单提议者的前提下是没有问题的，但是假如有多个提议者互不相让，那么就可能导致整个提议的过程进入了死循环。</p><p>Lamport 提出了 Multi Paxos 的算法思想。</p><p>Multi Paxos算法思想，简单说就是在多个提议者的情况下，选出一个Leader（领导者），由领导者作为唯一的提议者，这样就可以解决提议者冲突的问题。</p><h3 id="_5-raft算法是什么" tabindex="-1">5. Raft算法是什么？ <a class="header-anchor" href="#_5-raft算法是什么" aria-label="Permalink to &quot;5. Raft算法是什么？&quot;">​</a></h3><p><code>Raft</code> 也是一个 <strong>一致性算法</strong>，和 <code>Paxos</code> 目标相同。但它还有另一个名字 - <strong>易于理解的一致性算法</strong>。<code>Paxos</code> 和 <code>Raft</code> 都是为了实现 <strong>一致性</strong> 产生的。这个过程如同选举一样，<strong>参选者</strong> 需要说服 <strong>大多数选民</strong> (Server) 投票给他，一旦选定后就跟随其操作。<code>Paxos</code> 和 <code>Raft</code> 的区别在于选举的 <strong>具体过程</strong> 不同。</p><h3 id="_6-raft算法的工作流程" tabindex="-1">6. Raft算法的工作流程？ <a class="header-anchor" href="#_6-raft算法的工作流程" aria-label="Permalink to &quot;6. Raft算法的工作流程？&quot;">​</a></h3><h4 id="raft算法的角色" tabindex="-1">Raft算法的角色 <a class="header-anchor" href="#raft算法的角色" aria-label="Permalink to &quot;Raft算法的角色&quot;">​</a></h4><p><code>Raft</code> 协议将 <code>Server</code> 进程分为三种角色：</p><ul><li><strong>Leader（领导者）</strong></li><li><strong>Follower（跟随者）</strong></li><li><strong>Candidate（候选人）</strong></li></ul><p>就像一个民主社会，领导者由跟随者投票选出。刚开始没有 <strong>领导者</strong>，所有集群中的 <strong>参与者</strong> 都是 <strong>跟随者</strong>。</p><p>那么首先开启一轮大选。在大选期间 <strong>所有跟随者</strong> 都能参与竞选，这时所有跟随者的角色就变成了 <strong>候选人</strong>，民主投票选出领袖后就开始了这届领袖的任期，然后选举结束，所有除 <strong>领导者</strong> 的 <strong>候选人</strong> 又变回 <strong>跟随者</strong> 服从领导者领导。</p><p>这里提到一个概念 <strong>「任期」</strong>，用术语 <code>Term</code> 表达。</p><p>三类角色的变迁图如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211749761.png" alt="image-20230221174952694" style="zoom:67%;"><p>Raft三种角色变迁图</p><h4 id="leader选举过程" tabindex="-1">Leader选举过程 <a class="header-anchor" href="#leader选举过程" aria-label="Permalink to &quot;Leader选举过程&quot;">​</a></h4><p>Raft 使用心跳（heartbeat）触发Leader选举。当Server启动时，初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat，就会等待一段随机的时间后发起一次Leader选举。</p><p>Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送 RequestVote RPC 。结果有以下三种情况：</p><ul><li>赢得了多数（超过1/2）的选票，成功选举为Leader；</li><li>收到了Leader的消息，表示有其它服务器已经抢先当选了Leader；</li><li>没有Server赢得多数的选票，Leader选举失败，等待选举时间超时（<code>Election Timeout</code>）后发起下一次选举。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211749349.png" alt="image-20230221174938271" style="zoom:67%;"><p>Leader选举</p><p>选出 <code>Leader</code> 后，<code>Leader</code> 通过 <strong>定期</strong> 向所有 <code>Follower</code> 发送 <strong>心跳信息</strong> 维持其统治。若 <code>Follower</code> 一段时间未收到 <code>Leader</code> 的 <strong>心跳</strong>，则认为 <code>Leader</code> 可能已经挂了，然后再次发起 <strong>选举</strong> 过程。</p><h2 id="分布式设计" tabindex="-1">分布式设计 <a class="header-anchor" href="#分布式设计" aria-label="Permalink to &quot;分布式设计&quot;">​</a></h2><h3 id="_1-说说什么是幂等性" tabindex="-1">1. 说说什么是幂等性？ <a class="header-anchor" href="#_1-说说什么是幂等性" aria-label="Permalink to &quot;1. 说说什么是幂等性？&quot;">​</a></h3><blockquote><p>什么是幂等性？</p></blockquote><p>幂等性是一个数学概念，用在接口上：用在接口上就可以理解为：<strong>同一个接口，多次发出同一个请求，请求的结果是一致的。</strong></p><p>简单说，就是多次调用如一次。</p><blockquote><p>什么是幂等性问题？</p></blockquote><p>在系统的运行中，可能会出现这样的问题：</p><ol><li>用户在填写某些<code>form表单</code>时，保存按钮不小心快速点了两次，表中竟然产生了两条重复的数据，只是id不一样。</li><li>开发人员在项目中为了解决<code>接口超时</code>问题，通常会引入了<code>重试机制</code>。第一次请求接口超时了，请求方没能及时获取返回结果（此时有可能已经成功了），于是会对该请求重试几次，这样也会产生重复的数据。</li><li>mq消费者在读取消息时，有时候会读取到<code>重复消息</code>，也会产生重复的数据。</li></ol><p>这些都是常见的幂等性问题。</p><p>在分布式系统里，只要下游服务有写（保存、更新）的操作，都有可能会产生幂等性问题。</p><p>PS:幂等和防重有些不同，防重强调的防止数据重复，幂等强调的是多次调用如一次，防重包含幂等。</p><h3 id="_2-怎么保证接口幂等性" tabindex="-1">2. 怎么保证接口幂等性？ <a class="header-anchor" href="#_2-怎么保证接口幂等性" aria-label="Permalink to &quot;2. 怎么保证接口幂等性？&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211749169.png" alt="image-20230221174918108" style="zoom:67%;"><p>接口幂等性</p><ol><li><p>insert前先select</p><p>在保存数据的接口中，在<code>insert</code>前，先根据<code>requestId</code>等字段先<code>select</code>一下数据。如果该数据已存在，则直接返回，如果不存在，才执行 <code>insert</code>操作。</p></li><li><p>加唯一索引</p><p>加唯一索引是个非常简单但很有效的办法，如果重复插入数据的话，就会抛出异常，为了保证幂等性，一般需要捕获这个异常。</p><p>如果是<code>java</code>程序需要捕获：<code>DuplicateKeyException</code>异常，如果使用了<code>spring</code>框架还需要捕获：<code>MySQLIntegrityConstraintViolationException</code>异常。</p></li><li><p>加悲观锁</p><p>更新逻辑，比如更新用户账户余额，可以加悲观锁，把对应用户的哪一行数据锁住。同一时刻只允许一个请求获得锁，其他请求则等待。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">select * from user id=123 for update;</span></span></code></pre></div><p>这种方式有一个缺点，获取不到锁的请求一般只能报失败，比较难保证接口返回相同值。</p></li><li><p>加乐观锁</p><p>更新逻辑，也可以用乐观锁，性能更好。可以在表中增加一个<code>timestamp</code>或者<code>version</code>字段，例如<code>version</code>:</p><p>在更新前，先查询一下数据，将version也作为更新的条件，同时也更新version：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">update user set amount=amount+100,version=version+1 where id=123 and version=1;</span></span></code></pre></div><p>更新成功后，version增加，重复更新请求进来就无法更新了。</p></li><li><p>建防重表</p><p>有时候表中并非所有的场景都不允许产生重复的数据，只有某些特定场景才不允许。这时候，就可以使用防重表的方式。</p><p>例如消息消费中，创建防重表，存储消息的唯一ID，消费时先去查询是否已经消费，已经消费直接返回成功。</p></li><li><p>状态机</p><p>有些业务表是有状态的，比如订单表中有：1-下单、2-已支付、3-完成、4-撤销等状态，可以通过限制状态的流动来完成幂等。</p></li><li><p>分布式锁</p><p>直接在数据库上加锁的做法性能不够友好，可以使用分布式锁的方式，目前最流行的分布式锁实现是通过Redis，具体实现一般都是使用Redission框架。</p></li><li><p>token机制</p><p>请求接口之前，需要先获取一个唯一的token，再带着这个token去完成业务操作，服务端根据这个token是否存在，来判断是否是重复的请求。</p></li></ol><h2 id="分布式限流" tabindex="-1">分布式限流 <a class="header-anchor" href="#分布式限流" aria-label="Permalink to &quot;分布式限流&quot;">​</a></h2><h3 id="你了解哪些限流算法" tabindex="-1">你了解哪些限流算法？ <a class="header-anchor" href="#你了解哪些限流算法" aria-label="Permalink to &quot;你了解哪些限流算法？&quot;">​</a></h3><h4 id="计数器" tabindex="-1">计数器 <a class="header-anchor" href="#计数器" aria-label="Permalink to &quot;计数器&quot;">​</a></h4><blockquote><p>计数器比较简单粗暴，比如我们要限制1s能够通过的请求数，实现的思路就是从第一个请求进来开始计时，在接下来的1s内，每个请求进来请求数就+1，超过最大请求数的请求会被拒绝，等到1s结束后计数清零，重新开始计数。</p></blockquote><blockquote><p>这种方式有个很大的弊端：比如前10ms已经通过了最大的请求数，那么后面的990ms的请求只能拒绝，这种现象叫做“突刺现象”。</p></blockquote><h4 id="漏桶算法" tabindex="-1">漏桶算法 <a class="header-anchor" href="#漏桶算法" aria-label="Permalink to &quot;漏桶算法&quot;">​</a></h4><blockquote><p>就是桶底出水的速度恒定，进水的速度可能快慢不一，但是当进水量大于出水量的时候，水会被装在桶里，不会直接被丢弃；但是桶也是有容量限制的，当桶装满水后溢出的部分还是会被丢弃的。</p></blockquote><blockquote><p><strong>算法实现</strong>：可以准备一个队列来保存暂时处理不了的请求，然后通过一个线程池定期从队列中获取请求来执行。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211748683.png" alt="image-20230221174856605" style="zoom:33%;"><h4 id="令牌桶算法" tabindex="-1">令牌桶算法 <a class="header-anchor" href="#令牌桶算法" aria-label="Permalink to &quot;令牌桶算法&quot;">​</a></h4><p>令牌桶就是生产访问令牌的一个地方，生产的速度恒定，用户访问的时候当桶中有令牌时就可以访问，否则触发限流</p><p><strong>实现方案</strong>：Guava RateLimiter限流</p><p>Guava RateLimiter是一个谷歌提供的限流，其基于令牌桶算法，比较适用于单实例的系统。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2023.3.30/202302211748099.png" alt="image-20230221174830021" style="zoom:33%;"><p>这一期的分布式面试题就整理到这里了，主要是偏理论的一些问题，分布式其实是个很大的类型，比如分布式调用、分布式治理……所以，这篇文章只是个开始，后面还会有分布式调用（RPC）、微服务相关的主题文章，敬请期待。</p><h1 id="cap和base理论" tabindex="-1">CAP和BASE理论 <a class="header-anchor" href="#cap和base理论" aria-label="Permalink to &quot;CAP和BASE理论&quot;">​</a></h1><h2 id="cap理论" tabindex="-1">CAP理论 <a class="header-anchor" href="#cap理论" aria-label="Permalink to &quot;CAP理论&quot;">​</a></h2><h3 id="什么是cap理论" tabindex="-1">什么是CAP理论 <a class="header-anchor" href="#什么是cap理论" aria-label="Permalink to &quot;什么是CAP理论&quot;">​</a></h3><blockquote><p>CAP原理是现代分布式系统的理论基石，好比是分布式领域的牛顿定律。所有的分布式系统都是基于CAP理论来考虑和设计的。CAP理论的核心重点就是描述了任何一个分布式系统最多只能满足以下三个特性中的两个，<strong>所以在学习分布式系统和设计之前，我们必须了解和理解什么是CAP理论</strong></p></blockquote><h3 id="cap" tabindex="-1">CAP <a class="header-anchor" href="#cap" aria-label="Permalink to &quot;CAP&quot;">​</a></h3><p>Consistency（一致性，强一致性）</p><blockquote><p>这里一致性的意思是在分布式系统中，对多副本数据的读操作总是能读到之前写操作完成的结果, 说白了就要满足强一致性; 比如一个数据有多个存储节点，我在A节点对数据做出了更新，而A节点需要将数据同步到节点B和节点C甚至更多，同步虽然快，但也需要一定的时间，如果这个时间段内有并发请求过来读取这个数据，请求被负载均衡，就有可能出现多个请求读取到的数据不一致的问题，这是因为有的请求可能会落到还未完成数据同步的节点上；一致性就是为了避免出现这样的情况</p></blockquote><p>Availability（可用性）</p><blockquote><p>即在某个组件的集群环境中，如果该组件的某个或多个节点发生了故障，在这些节点故障处理完之前，这个分布式系统也要能保证该组件能正常提供服务，不会因为部分节点的瘫痪而牵连整个服务；通常集群多副本环境本身就可以保证可用性，说白了就是保证服务的高可用</p></blockquote><p>Partition Tolerance（分区容错性）</p><blockquote><p>分区容错性是这里最不好理解的概念，但是也不复杂。我们知道现实生活中可能会出现机器故障，机房停电，网络故障等客观现象，而集群的环境更是增加了重现的概率。就是因为这些可能会出现的问题，从而导致某个时间段，集群节点数据出现分区现象，所以分区容错性的意思就是系统容忍短暂出现数据分区的情况，等故障修复，再进行分区数据整合，补偿分区时造成的错误，但在数据分区时无法保证数据强一致性或可用性</p></blockquote><blockquote><p>什么是数据分区？ 分区就是因为集群节点之前无法通信，比如机房A无法跟机房B通信，从而造成机房A的节点有部分机房B没有的新数据，而机房B的节点也有部分机房A没有的新数据，但碍于无法通信，所以无法数据同步，从而造成数据的分区，每个分区的数据都不完整，只有合并在一起才是一个完整的数据；但情况也不仅限于通信故障，就比如数据同步时间过长，也有可能发生数据分区，所以分区对通信时限有严格的要求，系统只要在指定时间内不能达成数据的一致性，比如通信故障，复制同步时间过长，就意味着可能会发生数据分区</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/B279WL06QYwSrNJvIdMicA04micOcUV32VaDaO1P28MPdFZRh5mOhH48OtqaHGRywL8vzYribThjIjWDZMbT28CPg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="acid中c与cap定理中c的区别" tabindex="-1">ACID中C与CAP定理中C的区别 <a class="header-anchor" href="#acid中c与cap定理中c的区别" aria-label="Permalink to &quot;ACID中C与CAP定理中C的区别&quot;">​</a></h3><blockquote><p>ACID理论和CAP理论都有一个C，也都叫一致性Consistent, 所以很多人都会把这两个概念当做是一个概念，把自己弄的迷迷糊糊，团团转。没错，我说的就是自己，shame…</p></blockquote><p><strong>首先我要声明，这两个C肯定是有区别的：</strong></p><blockquote><p>ACID的C指的是事务中的一致性，在一串对数据进行修改的操作中，保证数据的正确性。即数据在事务期间的多个操作中，数据不会凭空的消失或增加，数据的每一个增删改操作都是有因果关系的；比如用户A想用户B转了200块钱，不会出现用户A扣了款，而用户B没有收到的情况</p></blockquote><blockquote><p>CAP的C则指的是分布式环境中，多服务之间的复制是异步，需要一定耗时的，不是即时瞬间完成。所以可能会造成某个节点的数据修改，将修改的数据同步到其他服务需要一定的时间，如果此时有并发请求过来，请求负载均衡到多个节点，可能会出现多个节点获取的数据不一致的问题，因为请求有可能会落在还没完成数据同步的节点上；而C就是为了做到在分布式集群环境读到的数据是一致的；当然这里的C也有分类，如强一致性，弱一致性，最终一致性，即ACID的C着重强调单数据库事务操作时，要保证数据的完整和正确性;而CAP理论中的C指的是对一个数据多个备份的读写一致性</p></blockquote><h3 id="三选二的艰难抉择" tabindex="-1">三选二的艰难抉择 <a class="header-anchor" href="#三选二的艰难抉择" aria-label="Permalink to &quot;三选二的艰难抉择&quot;">​</a></h3><p><strong>总之CAP的理论核心就是C , A ,P不能共存，只可能三选二，以求最大能保证的有利利益</strong></p><p>因为长时间无法达成数据一致性，就可能造成数据分区，所以要满足P，就必须在A和C做出选择：</p><blockquote><ul><li>(<strong>不去满足一致性C</strong>)即我不追求数据强一致性，在集群节点某时刻出现数据不一致的情况下，我可以去保证分区容错性，容忍分区的出现，之后再做补偿 , 即做到AP</li><li>(<strong>不去满足可用性A</strong>)即在可能发生数据分区的时候，在故障解决之前，我去停止集群节点的对外服务，避免出现对数据的增，删，改操作，让数据不被改动，这样就不会导致数据的分区了,做到CP</li><li>(<strong>不去满足分区容错性</strong>P)则代表当没有发生数据分区时，系统可以保证A和C，但是如果发生数据分区，因为不满足分区容错，即无法容忍分区的存在，就必定需要A,C二选一，最终只剩A或者C , 即 CA</li></ul></blockquote><p>所以我们可以知道</p><ul><li>CA - 单节点系统满足一致性，可用性，因为单节点，自然没有P的问题，但是没有扩展性，非常容易遇到性能瓶颈，其实本质上单节点系统也不需要考虑CAP问题。</li><li>CP - 满足一致性，分区容忍必的系统，通常性能不是特别高，因为为了保证数据强一致性，必须等待所有节点完成数据同步才能对外提供服务。</li><li>AP - 满足可用性，分区容忍性的系统，通常可能对一致性要求低一些，性能高，一般追求最终一致性</li></ul><p><strong>从上面看来，P几乎是必不可少的，因为AC最终会退化成A或C，除非选择做一个单节点服务，但这样也就不是分布式集群系统了，CAP理论就没有卵子用了， 所以AC几乎就是一个最不好的选择，所以通常情况下的实践都是在CP或AP中做出选择</strong></p><h3 id="强一致性-弱一致性-最终一致性" tabindex="-1">强一致性，弱一致性，最终一致性… <a class="header-anchor" href="#强一致性-弱一致性-最终一致性" aria-label="Permalink to &quot;强一致性，弱一致性，最终一致性…&quot;">​</a></h3><h4 id="强一致性" tabindex="-1">强一致性 <a class="header-anchor" href="#强一致性" aria-label="Permalink to &quot;强一致性&quot;">​</a></h4><p>强一致性就是在任何时刻都从集群节点中获取的数据都是一致性</p><p>原子一致性</p><p>线性一致性</p><h4 id="弱一致性" tabindex="-1">弱一致性 <a class="header-anchor" href="#弱一致性" aria-label="Permalink to &quot;弱一致性&quot;">​</a></h4><p>系统中的某个数据被更新后，后续对该数据的读取操作可能得到更新后的值，也可能是更改前的值。可以有多种实现方式。</p><h4 id="最终一致性" tabindex="-1">最终一致性 <a class="header-anchor" href="#最终一致性" aria-label="Permalink to &quot;最终一致性&quot;">​</a></h4><blockquote><p>最终一致性:最终一致性是弱一致性的一种形式，就是不保证在任意时刻任意节点上的同一份数据都是相同的，但是随着时间的迁移，不同节点上的同一份数据总是在向趋同的方向变化；总之就是一段时间后，集群节点的数据会最终达到一致状态。</p></blockquote><blockquote><p>因果一致性:如果进程A通知进程B它已更新了一个数据项，那么进程B的后续访问将获得的是进程A更新后的值</p></blockquote><blockquote><p>读己之所写一致性:当进程A自己更新一个数据项之后，它肯定会访问到自己更新过的值，绝不会看到旧值。这是因果一致性模型的一个特例。</p></blockquote><blockquote><p>会话一致性:这是上一个模型的实用版本，它把访问存储系统的进程放到会话的上下文中。只要会话还存在，系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止，就要建立新的会话，而且系统的保证不会延续到新的会话。单调读一致性:如果进程已经看到过数据对象的某个值，那么任何后续访问都不会返回在那个值之前的值。</p></blockquote><p>单调写一致性:系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性，就非常难以编程了</p><p>我们知道强一致性就是<code>C</code>嘛，通常在保证<code>AP</code>的分布式系统中，都是通过选择最终一致性来弥补数据分区期间的造成的错误，也就是忽略因出现故障或数据同步未完成而导致的数据分区情况，在故障解决后，或数据同步后对分区时期造成的影响做数据补偿和合并，这样就可以弥补分区期间发生的任何错误</p><h3 id="ca-ap-cp的实践者" tabindex="-1">CA，AP，CP的实践者 <a class="header-anchor" href="#ca-ap-cp的实践者" aria-label="Permalink to &quot;CA，AP，CP的实践者&quot;">​</a></h3><h4 id="ca" tabindex="-1">CA <a class="header-anchor" href="#ca" aria-label="Permalink to &quot;CA&quot;">​</a></h4><blockquote><p>单点部署的数据库，没有集群环境，必然可以保证数据的可用性和一致性，<code>MySQL</code>一般被归类为CA，但也看设置情况，如果是主从同步复制，基本就符合CA，如果是异步复制，那就不一定满足CA了</p></blockquote><h4 id="cp" tabindex="-1">CP <a class="header-anchor" href="#cp" aria-label="Permalink to &quot;CP&quot;">​</a></h4><blockquote><p><code>ZooKeeper</code>就是CP的实践者，即任何时刻对ZooKeeper的访问请求能得到一致的数据结果，同时系统对网络分割具备容错性；但是它不能保证每次服务请求的可用性（就是在极端环境下，ZooKeeper可能会丢弃一些请求，消费者程序需要重新请求才能获得结果）<code>Mongodb</code>一般被归类为CP</p></blockquote><h4 id="ap" tabindex="-1">AP <a class="header-anchor" href="#ap" aria-label="Permalink to &quot;AP&quot;">​</a></h4><blockquote><p><code>Eureka</code>遵循的就是AP, 因为针对同一个服务，即使注册中心的不同节点保存的服务提供者信息不完全相同，但也并不会造成灾难性的后果，因为对于服务消费者来说，能消费才是最重要的，拿到的服务提供者即使不正错，大不了重试，那也好过因为无法获取服务提供者信息而不能去消费好</p></blockquote><h3 id="cap的小结" tabindex="-1">CAP的小结 <a class="header-anchor" href="#cap的小结" aria-label="Permalink to &quot;CAP的小结&quot;">​</a></h3><blockquote><ul><li>分区是常态，无法避免，CAP三者不可共存，所以必然是三选二</li><li>可用性和一致性是一对冤家，正是因为要做到高可用，所以才会出现不一致性(<strong>就因为存在了多个节点，才会出现数据复制和通信问题</strong>)；也正是因为出现不一致性，才可能要停止集群服务，防止出现分区</li><li>很多情况，一些实践者并不完全的去追求CP或是AP, 他们可能是尽量的去保证可用性，也尽量的去保证一致性，同时又满足一定的分区</li></ul></blockquote><h2 id="base理论" tabindex="-1">BASE理论 <a class="header-anchor" href="#base理论" aria-label="Permalink to &quot;BASE理论&quot;">​</a></h2><h3 id="什么是base理论呢" tabindex="-1">什么是BASE理论呢 <a class="header-anchor" href="#什么是base理论呢" aria-label="Permalink to &quot;什么是BASE理论呢&quot;">​</a></h3><p>BASE理论就是对CAP理论中的一致性和可用性权衡之后的结果，指的就是分布式系统中，如果无法做到强一致性，那么就用最终一致性去代替，<strong>让分布式系统满足三个特性</strong></p><ul><li>基本可用（<code>Basically Available</code>）</li><li>软状态（<code>Soft State</code>）</li><li>最终一致性（<code>Eventual Consistency</code>）</li></ul><h3 id="base" tabindex="-1">BASE <a class="header-anchor" href="#base" aria-label="Permalink to &quot;BASE&quot;">​</a></h3><p>基本可用（Basically Available）</p><blockquote><p>基本可用的意思就是分布式系统如果出现不可预知的故障时，允许损失部分的可用性，但并不是整个系统都不可用，即保证系统核心服务可用即可；比如实际开发中，出现部分服务故障，我们可以让系统的响应时间适当变长，或者限流，降低消费，甚至是服务降级，让某个服务暂时无法提供服务</p></blockquote><p>软状态（Soft State）</p><blockquote><p>软状态就是指系统中的数据可以存在中间状态， 并该中间状态不会影响到系统的可用性；换个说法就是允许系统中部分结点的数据同步存在时延问题，但这个时延不会影响到系统的可用性</p></blockquote><p>最终一致性（Eventual Consistency）</p><blockquote><p>最终一致性强调的是所有的数据副本，在经过一段时间的同步之后，最终都能够达到数据一致的状态。既最终一致性的本质是需要系统保证数据的最终一致性，而不每时每刻的强一致性。但也要保证非一致窗口时期不会对系统数据造成危害</p></blockquote><h3 id="base与acid" tabindex="-1">BASE与ACID <a class="header-anchor" href="#base与acid" aria-label="Permalink to &quot;BASE与ACID&quot;">​</a></h3><blockquote><p><code>ACID</code>是传统数据库常用的设计理念，追求强一致性模型，例如银行的转账场景，最求数据的绝对可靠。而<code>BASE</code>支持的是大型分布式系统，提出通过牺牲强一致性获得高可用性。</p></blockquote><blockquote><p>虽然<code>ACID</code>和<code>BASE</code>代表了两种截然相反的设计哲学，在分布式系统设计的场景中，系统组件对一致性要求是不同的，因此<code>ACID</code>和<code>BASE</code>又会结合使用</p></blockquote><h3 id="base小结" tabindex="-1">BASE小结 <a class="header-anchor" href="#base小结" aria-label="Permalink to &quot;BASE小结&quot;">​</a></h3><blockquote><p>总的来说，<code>BASE</code>理论面向的是大型高可用可扩展的分布式系统，是对<code>CAP</code>理论的一些弱化和妥协，为了保证可用性，对强一致性进行了削弱</p></blockquote><blockquote><p>在了解了<code>CAP</code>和<code>BASE</code>理论之后，就可以自己去了解一下<code>Redis</code>, <code>MongoDB</code>, <code>MySQL</code>这些数据库怎么处理集群状态下的数据复制了，相信这也是一个很好的加深基础的问题；同时也可以去了解一下通常系统是怎么去实现最终一致性的，怎么解决非一致性窗口期间出现一些数据问题</p></blockquote><h1 id="分布式系统的技术栈" tabindex="-1">分布式系统的技术栈 <a class="header-anchor" href="#分布式系统的技术栈" aria-label="Permalink to &quot;分布式系统的技术栈&quot;">​</a></h1><p>不知道大家还记得去年 B 站（哔哩哔哩）挂了那次严重的事故不，记得当时在全网也是引起了热议。</p><p>离当时过去刚好一年多的时间，今天看到 B 站前两天在公众号上发的复盘报告。2021.07.13 我们是这样崩的 文章从八个方面全链路再现了当时事故发生和处理的全过程：</p><p>包括<strong>至暗时刻，初因定位，故障止损，根因定位，原因说明，问题分析，优化改进，总结。</strong></p><p>不知道大家看过没，我全文看了一下，看完还挺唏嘘的，最终原因竟然是一个字符串类型的数字参数 0 导致的死循环。</p><p>另外，从官方的这份报告里，我看到了多次提及多活，容灾，分布式这个词汇，异地多活是常见的分布式系统保证架构稳定性的一个方案。</p><p>毕竟这么体量的公司，系统架构肯定和分布式是绕不开的。<strong>那咱们以此来看看，分布式系统里面都有哪些技术栈呢？</strong></p><p>**1、大流量处理。**通过集群技术把大规模并发请求的负载分散到不同的机器上。</p><p><strong>2、关键业务保护</strong>。提高后台服务的可用性，</p><p>说白了就是干两件事。一是提高整体架构的吞吐量，服务更多的并发和流量，二是为了提高系统的稳定性，让系统的可用性更高。</p><h2 id="提高架构的性能" tabindex="-1">提高架构的性能 <a class="header-anchor" href="#提高架构的性能" aria-label="Permalink to &quot;提高架构的性能&quot;">​</a></h2><p>咱们先来看看，提高系统性能的常用技术。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207221443324.png" alt="image-20220722144317235" style="zoom:80%;"><ul><li><p><strong>缓存系统。</strong><code>加入缓存系统，可以有效地提高系统的访问能力</code>。比如从前端的浏览器，到网络，再到后端的服务，底层的数据库、文件系统、硬盘和 CPU，全都有缓存，这是提高快速访问能力最有效的手段。对于分布式系统下的缓存系统，需要的是一个缓存集群。比如用一个 Proxy 来做缓存的分片和路由。</p></li><li><p>**负载均衡系统。**负载均衡系统是水平扩展的关键技术，它可以<code>使用多台机器来共同分担一部分流量请求</code>。</p></li><li><p><strong>异步调用。</strong><code>异步系统主要通过消息队列来对请求做排队处理</code>，这样可以把前端的请求的峰值给“削平”了，所谓削峰填谷。而后端通过自己能够处理的速度来处理请求。这样可以增加系统的吞吐量，但是实时性就会有影响。同时，还会引入消息丢失的问题，所以要对消息做持久化，这会造成“有状态”的结点，从而增加了服务调度的难度。</p></li><li><p><strong>数据分区和数据镜像。</strong><code>数据分区是把数据按一定的方式分成多个区（比如通过地理位置）</code>，<code>不同的数据区来分担不同区的流量</code>。这需要一个数据路由的中间件，而数据镜像是把一个数据库镜像成多份一样的数据，这样就不需要数据路由的中间件了。可以在任意结点上进行读写，内部会自行同步数据。然而，数据镜像中最大的问题就是数据的一致性问题。</p></li></ul><h2 id="提高架构的稳定性" tabindex="-1">提高架构的稳定性 <a class="header-anchor" href="#提高架构的稳定性" aria-label="Permalink to &quot;提高架构的稳定性&quot;">​</a></h2><p>接下来，咱们再来看看提高系统系统稳定性的一些常用技术。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/j5D4MI5U9vXFibkldSW1ia5leDAv8UrDVNI5tS9uJEqrwzYKqMFuyLf4mRwk0M4rl8Bgn5OXyLMYQavkiaCV1ibkMA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><ul><li>**服务拆分。**服务拆分主要有两个目的：一是为了<code>隔离故障</code>，二是为了<code>重用服务模块</code>。但服务拆分完之后，会引入<code>服务调用间的依赖问题</code>。</li><li><strong>服务冗余。</strong> 服务冗余是为了<code>去除单点故障</code>，并可以<code>支持服务的弹性伸缩</code>，以及<code>故障迁移</code>。然而，对于一些有状态的服务来说，冗余这些有状态的服务带来了更高的复杂性。其中一个是弹性伸缩时，需要考虑数据的复制或是重新分片，迁移的时候还要迁移数据到其它机器上。</li><li>**限流降级。**当系统实在扛不住压力时，只能通过<code>限流或者功能降级</code>的方式来停掉一部分服务，或是<code>拒绝一部分用户</code>，以确保整个架构不会挂掉。这些技术属于保护措施。</li><li>**高可用架构。**通常来说高可用架构是从冗余架构的角度来保障可用性。比如，<code>多租户隔离，灾备多活</code>，或是数据可以在其中复制保持一致性的集群。总之，就是为了不出单点故障。</li><li>**高可用运维。**高可用运维指的是 DevOps 中的 <code>CI/CD（持续集成 / 持续部署）</code>。一个良好的运维应该是一条很流畅的软件发布管线，其中做了足够的自动化测试，还可以做相应的灰度发布，以及对线上系统的自动化控制。这样，可以做到“计划内”或是“非计划内”的宕机事件的时长最短。</li></ul><h2 id="分布式和集群的区别" tabindex="-1">分布式和集群的区别 <a class="header-anchor" href="#分布式和集群的区别" aria-label="Permalink to &quot;分布式和集群的区别&quot;">​</a></h2><h3 id="_1-什么是分布式" tabindex="-1">1. 什么是分布式 ? <a class="header-anchor" href="#_1-什么是分布式" aria-label="Permalink to &quot;1. 什么是分布式 ?&quot;">​</a></h3><p>分布式系统一定是由多个节点组成的系统。</p><p>其中，节点指的是计算机服务器，而且这些节点一般不是孤立的，而是互通的。</p><p>这些连通的节点上部署了我们的节点，并且相互的操作会有协同。</p><p>分布式系统对于用户而言，他们面对的就是一个服务器，提供用户需要的服务而已，</p><p>而实际上这些服务是通过背后的众多服务器组成的一个分布式系统，因此分布式系统看起来像是一个超级计算机一样。</p><h3 id="_2-分布式与集群的区别" tabindex="-1">2. 分布式与集群的区别 <a class="header-anchor" href="#_2-分布式与集群的区别" aria-label="Permalink to &quot;2. 分布式与集群的区别&quot;">​</a></h3><h5 id="集群" tabindex="-1">集群 <a class="header-anchor" href="#集群" aria-label="Permalink to &quot;集群&quot;">​</a></h5><p>集群是指在几个服务器上部署相同的应用程序来分担客户端的请求。</p><p>它是同一个系统部署在不同的服务器上，比如一个登陆系统部署在不同的服务器上。</p><p>好比 多个人一起做同样的事。</p><p>集群主要的使用场景是为了分担请求的压力。</p><p>但是，当压力进一步增大的时候，可能在需要存储的部分，比如mysql无法面对大量的“写压力”。</p><p>因为在mysql做成集群之后，主要的写压力还是在master的机器上，其他slave机器无法分担写压力，这时，就引出了“分布式”。</p><h5 id="分布式" tabindex="-1">分布式 <a class="header-anchor" href="#分布式" aria-label="Permalink to &quot;分布式&quot;">​</a></h5><p>分布式是指多个系统协同合作完成一个特定任务的系统。</p><p>它是不同的系统部署在不同的服务器上，服务器之间相互调用。</p><p>好比 多个人一起做不同的事。</p><p>分布式是解决中心化管理的问题，把所有的任务叠加到一个节点处理，太慢了。</p><p>所以把一个大问题拆分为多个小问题，并分别解决，最终协同合作。</p><p>分布式的主要工作是分解任务，把职能拆解。</p><p>分布式的主要应用场景是单台机器已经无法满足这种性能的要求，必须要融合多个节点，并且节点之间的相关部分是有交互的。</p><p>相当于在写mysql的时候，每个节点存储部分数据（分库分表），这就是分布式存储的由来。</p><p>存储一些非结构化数据：静态文件、图片、pdf、小视频 ... 这些也是分布式文件系统的由来。</p><h5 id="生活中的例子" tabindex="-1">生活中的例子 <a class="header-anchor" href="#生活中的例子" aria-label="Permalink to &quot;生活中的例子&quot;">​</a></h5><p>小饭店原来只有一个厨师，切菜洗菜备料炒菜全干。</p><p>后来客人多了，厨房一个厨师忙不过来，又请了个厨师，两个厨师炒一样的菜，这两个厨师的关系是集群。</p><p>为了让厨师专心炒菜，把菜做到极致，又请了个配菜师负责切菜，备菜，备料，厨师和配菜师的关系是分布式，</p><p>一个配菜师也忙不过来了，又请了个配菜师，两个配菜师关系是集群。</p><h3 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to &quot;总结&quot;">​</a></h3><ul><li>分布式：把一个大业务拆分成多个子业务，每个子业务都是一套独立的系统，子业务之间相互协作最终完成整体的大业务。</li><li>集群：把处理同一个业务的系统部署多个节点 。</li></ul><p>把一套系统拆分成不同的子系统部署在不同服务器上，这叫分布式。</p><p>把多个相同的系统部署在不同的服务器上，这叫集群。部署在不同服务器上的相同系统必然要做“负载均衡”。</p><p>集群主要是简单加机器解决问题，对于问题本身不做任何分解。</p><p>分布式处理里必然涉及任务分解与答案归并。分布式中的某个子任务节点，可以是一个集群，该集群中的任一节点都作为一个完整的任务出现。</p><p>集群和分布式都是由多个节点组成，但集群中各节点间基本不需要通信协调，而分布式中各个节点的通信协调是必不可少的。</p><h1 id="分布式十大坑" tabindex="-1">分布式十大坑 <a class="header-anchor" href="#分布式十大坑" aria-label="Permalink to &quot;分布式十大坑&quot;">​</a></h1><p><a href="https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&amp;mid=2247488553&amp;idx=2&amp;sn=fa13e9698e59f5a5485d3d3d4b8ef2b1&amp;chksm=cf21cb00f8564216277806780c64e13c48fe32009f588349b3365afa8de97bd8ef192507bd50&amp;mpshare=1&amp;scene=23&amp;srcid=0718pKWolT5l4EneSkX7fAEt&amp;sharer_sharetime=1658151862730&amp;sharer_shareid=29b8a04db1dbd975e3bf4e9f47e7ac67#rd" target="_blank" rel="noreferrer">这三年被分布式坑惨了，曝光十大坑 (qq.com)</a></p><p>本篇主要内容如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111447217.png" alt="image-20220711144752166" style="zoom:67%;"><h2 id="前言" tabindex="-1">前言 <a class="header-anchor" href="#前言" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>我们都在讨论分布式，特别是面试的时候，不管是招初级软件工程师还是高级，都会要求懂分布式，甚至要求用过。传得沸沸扬扬的分布式到底是什么东东，有什么优势？</p><p>看过<code>火影</code>的同学肯定知道<code>漩涡鸣人</code>的招牌忍术：<code>多重影分身之术</code>。</p><ul><li>这个术有一个特别厉害的地方，<code>过程和心得</code>：多个分身的感受和经历都是相通的。比如 A 分身去找卡卡西（鸣人的老师）请教问题，那么其他分身也会知道 A 分身问的什么问题。</li><li><code>漩涡鸣人</code>有另外一个超级厉害的忍术，需要由几个影分身完成：<code>风遁·螺旋手里剑。</code>这个忍术是靠三个鸣人一起协作完成的。</li></ul><p>这两个忍术和分布式有什么关系？</p><ul><li>分布在不同地方的系统或服务，是彼此相互关联的。</li><li>分布式系统是分工合作的。</li></ul><p>案例：</p><ul><li>比如 Redis 的<code>哨兵机制</code>，可以知道集群环境下哪台 <code>Redis</code> 节点挂了。</li><li>Kafka的 <code>Leader 选举机制</code>，如果某个节点挂了，会从 <code>follower</code> 中重新选举一个 leader 出来。（leader 作为写数据的入口，follower 作为读的入口）</li></ul><p>那<code>多重影分身之术</code>有什么缺点？</p><ul><li>会消耗大量的查克拉。分布式系统同样具有这个问题，需要几倍的资源来支持。</li></ul><h3 id="对分布式的通俗理解" tabindex="-1">对分布式的通俗理解 <a class="header-anchor" href="#对分布式的通俗理解" aria-label="Permalink to &quot;对分布式的通俗理解&quot;">​</a></h3><ul><li>是一种工作方式</li><li>若干独立计算机的集合，这些计算机对于用户来说就像单个相关系统</li><li>将不同的业务分布在不同的地方</li></ul><h3 id="优势可以从两方面考虑-一个是宏观-一个是微观。" tabindex="-1">优势可以从两方面考虑：一个是宏观，一个是微观。 <a class="header-anchor" href="#优势可以从两方面考虑-一个是宏观-一个是微观。" aria-label="Permalink to &quot;优势可以从两方面考虑：一个是宏观，一个是微观。&quot;">​</a></h3><ul><li>宏观层面：多个功能模块糅合在一起的系统进行服务拆分，来解耦服务间的调用。</li><li>微观层面：将模块提供的服务分布到不同的机器或容器里，来扩大服务力度。</li></ul><h3 id="任何事物有阴必有阳-那分布式又会带来哪些问题呢" tabindex="-1">任何事物有阴必有阳，那分布式又会带来哪些问题呢？ <a class="header-anchor" href="#任何事物有阴必有阳-那分布式又会带来哪些问题呢" aria-label="Permalink to &quot;任何事物有阴必有阳，那分布式又会带来哪些问题呢？&quot;">​</a></h3><ul><li>需要更多优质人才懂分布式，人力成本增加</li><li>架构设计变得异常复杂，学习成本高</li><li>运维部署和维护成本显著增加</li><li>多服务间链路变长，开发排查问题难度加大</li><li>环境高可靠性问题</li><li>数据幂等性问题</li><li>数据的顺序问题</li><li>等等</li></ul><p>讲到<code>分布式</code>不得不知道 <code>CAP</code> 定理和 <code>Base</code> 理论，这里给不知道的同学做一个扫盲。</p><h3 id="cap-定理" tabindex="-1">CAP 定理 <a class="header-anchor" href="#cap-定理" aria-label="Permalink to &quot;CAP 定理&quot;">​</a></h3><p>在理论计算机科学中，CAP 定理指出对于一个分布式计算系统来说，不可能通是满足以下三点：</p><ul><li><p><strong>一致性（Consistency）</strong></p></li><li><ul><li>所有节点访问同一份最新的数据副本。</li></ul></li><li><p><strong>可用性（Availability）</strong></p></li><li><ul><li>每次请求都能获取到非错的响应，但不保证获取的数据为最新数据</li></ul></li><li><p><strong>分区容错性（Partition tolerance）</strong></p></li><li><ul><li>不能在时限内达成数据一致性，就意味着发生了分区的情况，必须就当前操作在 C 和 A 之间做出选择）</li></ul></li></ul><h3 id="base-理论" tabindex="-1">BASE 理论 <a class="header-anchor" href="#base-理论" aria-label="Permalink to &quot;BASE 理论&quot;">​</a></h3><p><code>BASE</code> 是 <code>Basically Available</code>（基本可用）、<code>Soft state</code>（软状态）和 <code>Eventually consistent</code>（最终一致性）三个短语的缩写。<code>BASE</code> 理论是对 <code>CAP</code> 中 <code>AP</code> 的一个扩展，通过牺牲强一致性来获得可用性，当出现故障允许部分不可用但要保证核心功能可用，允许数据在一段时间内是不一致的，但最终达到一致状态。满足 <code>BASE</code> 理论的事务，我们称之为<code>柔性事务</code>。</p><ul><li><strong>基本可用 ：</strong> 分布式系统在出现故障时，允许损失部分可用功能，保证核心功能可用。如电商网址交易付款出现问题来，商品依然可以正常浏览。</li><li><strong>软状态：</strong> 由于不要求强一致性，所以BASE允许系统中存在中间状态（也叫软状态），这个状态不影响系统可用性，如订单中的“支付中”、“数据同步中”等状态，待数据最终一致后状态改为“成功”状态。</li><li><strong>最终一致性：</strong> 最终一致是指的经过一段时间后，所有节点数据都将会达到一致。如订单的“支付中”状态，最终会变为“支付成功”或者“支付失败”，使订单状态与实际交易结果达成一致，但需要一定时间的延迟、等待。</li></ul><h1 id="一、分布式消息队列的坑" tabindex="-1">一、分布式消息队列的坑 <a class="header-anchor" href="#一、分布式消息队列的坑" aria-label="Permalink to &quot;一、分布式消息队列的坑&quot;">​</a></h1><h3 id="消息队列如何做分布式" tabindex="-1">消息队列如何做分布式？ <a class="header-anchor" href="#消息队列如何做分布式" aria-label="Permalink to &quot;消息队列如何做分布式？&quot;">​</a></h3><p>将消息队列里面的消息分摊到多个节点（指某台机器或容器）上，所有节点的消息队列之和就包含了所有消息。</p><h2 id="_1-消息队列的坑之非幂等" tabindex="-1">1. 消息队列的坑之非幂等 <a class="header-anchor" href="#_1-消息队列的坑之非幂等" aria-label="Permalink to &quot;1. 消息队列的坑之非幂等&quot;">​</a></h2><h3 id="幂等性概念" tabindex="-1">幂等性概念 <a class="header-anchor" href="#幂等性概念" aria-label="Permalink to &quot;幂等性概念&quot;">​</a></h3><p>所谓幂等性就是无论多少次操作和第一次的操作结果一样。如果消息被多次消费，很有可能造成数据的不一致。而如果消息不可避免地被消费多次，如果我们开发人员能通过技术手段保证数据的前后一致性，那也是可以接受的，这让我想起了 Java 并发编程中的 ABA 问题，如果出现了 [<a href="http://mp.weixin.qq.com/s?__biz=MzAwMjI0ODk0NA==&amp;mid=2451944205&amp;idx=1&amp;sn=472fab10dda17168a7a7b14a5f9e9ac7&amp;chksm=8d1c4a92ba6bc3849eebbd925009e0d61593fa8e3dd9a087725ffa55c0f3a38c3bb7646db83f&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">ABA问题</a>)，若能保证所有数据的前后一致性也能接受。</p><p><strong>场景分****析</strong></p><p><code>RabbitMQ</code>、<code>RocketMQ</code>、<code>Kafka</code> 消息队列中间件都有可能出现消息重复消费问题。这种问题并不是 MQ 自己保证的，而是需要开发人员来保证。</p><p>这几款消息队列中间都是是全球最牛的分布式消息队列，那肯定考虑到了消息的幂等性。我们以 Kafka 为例，看看 Kafka 是怎么保证消息队列的幂等性。</p><p>Kafka 有一个 <code>偏移量</code> 的概念，代表着消息的序号，每条消息写到消息队列都会有一个偏移量，消费者消费了数据之后，每过一段固定的时间，就会把消费过的消息的偏移量提交一下，表示已经消费过了，下次消费就从偏移量后面开始消费。</p><blockquote><p><code>坑：</code>当消费完消息后，还没来得及提交偏移量，系统就被关机了，那么未提交偏移量的消息则会再次被消费。</p></blockquote><p>如下图所示，队列中的数据 A、B、C，对应的偏移量分别为 100、101、102，都被消费者消费了，但是只有数据 A 的偏移量 100 提交成功，另外 2 个偏移量因系统重启而导致未及时提交。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111448616.png" alt="image-20220711144857566" style="zoom:67%;"><p>重启后，消费者又是拿偏移量 100 以后的数据，从偏移量 101 开始拿消息。所以数据 B 和数据 C 被重复消息。</p><p>如下图所示：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111457751.png" alt="image-20220711145735704" style="zoom:67%;"><p>重启后，重复消费消息</p><h3 id="避坑指南" tabindex="-1">避坑指南 <a class="header-anchor" href="#避坑指南" aria-label="Permalink to &quot;避坑指南&quot;">​</a></h3><ul><li><p>微信支付结果通知场景</p></li><li><ul><li>微信官方文档上提到微信支付通知结果可能会推送多次，需要开发者自行保证幂等性。第一次我们可以直接修改订单状态（如支付中 -&gt; 支付成功），第二次就根据订单状态来判断，如果不是支付中，则不进行订单处理逻辑。</li></ul></li><li><p>插入数据库场景</p></li><li><ul><li>每次插入数据时，先检查下数据库中是否有这条数据的主键 id，如果有，则进行更新操作。</li></ul></li><li><p>写 Redis 场景</p></li><li><ul><li>Redis 的 <code>Set</code> 操作天然幂等性，所以不用考虑 Redis 写数据的问题。</li></ul></li><li><p>其他场景方案</p></li><li><ul><li>生产者发送每条数据时，增加一个全局唯一 id，类似订单 id。每次消费时，先去 Redis 查下是否有这个 id，如果没有，则进行正常处理消息，且将 id 存到 Redis。如果查到有这个 id，说明之前消费过，则不要进行重复处理这条消息。</li><li>不同业务场景，可能会有不同的幂等性方案，大家选择合适的即可，上面的几种方案只是提供常见的解决思路。</li></ul></li></ul><h2 id="_2-消息队列的坑之消息丢失" tabindex="-1">2. 消息队列的坑之消息丢失 <a class="header-anchor" href="#_2-消息队列的坑之消息丢失" aria-label="Permalink to &quot;2. 消息队列的坑之消息丢失&quot;">​</a></h2><blockquote><p><code>坑:</code>消息丢失会带来什么问题？如果是订单下单、支付结果通知、扣费相关的消息丢失，则可能造成财务损失，如果量很大，就会给甲方带来巨大损失。</p></blockquote><p>那消息队列是否能保证消息不丢失呢？答案：否。主要有三种场景会导致消息丢失。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111457463.png" alt="image-20220711145717432" style="zoom:67%;"><h3 id="_1-生产者存放消息的过程中丢失消息" tabindex="-1">（1）生产者存放消息的过程中丢失消息 <a class="header-anchor" href="#_1-生产者存放消息的过程中丢失消息" aria-label="Permalink to &quot;（1）生产者存放消息的过程中丢失消息&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111456278.png" alt="image-20220711145638250" style="zoom:67%;"><h4 id="解决方案" tabindex="-1">解决方案 <a class="header-anchor" href="#解决方案" aria-label="Permalink to &quot;解决方案&quot;">​</a></h4><ul><li>事务机制（不推荐，异步方式）</li></ul><p>对于 RabbitMQ 来说，生产者发送数据之前开启 RabbitMQ 的<strong>事务机制</strong><code>channel.txselect</code> ，如果消息没有进队列，则生产者受到异常报错，并进行回滚 <code>channel.txRollback</code>，然后重试发送消息；如果收到了消息，则可以提交事务 <code>channel.txCommit</code>。但这是一个同步的操作，会影响性能。</p><ul><li>confirm 机制（推荐，异步方式）</li></ul><p>我们可以采用另外一种模式：<code>confirm</code> 模式来解决同步机制的性能问题。每次生产者发送的消息都会分配一个唯一的 id，如果写入到了 RabbitMQ 队列中，则 RabbitMQ 会回传一个 <code>ack</code> 消息，说明这个消息接收成功。如果 RabbitMQ 没能处理这个消息，则回调 <code>nack</code> 接口。说明需要重试发送消息。</p><p>也可以自定义超时时间 + 消息 id 来实现超时等待后重试机制。但可能出现的问题是调用 ack 接口时失败了，所以会出现消息被发送两次的问题，这个时候就需要保证消费者消费消息的幂等性。</p><h4 id="事务模式-和-confirm-模式的区别" tabindex="-1"><code>事务模式</code> 和 <code>confirm</code> 模式的区别： <a class="header-anchor" href="#事务模式-和-confirm-模式的区别" aria-label="Permalink to &quot;`事务模式` 和 `confirm` 模式的区别：&quot;">​</a></h4><ul><li>事务机制是同步的，提交事务后悔被<strong>阻塞</strong>直到提交事务完成后。</li><li>confirm 模式异步接收通知，但可能<strong>接收不到通知</strong>。需要考虑接收不到通知的场景。</li></ul><h3 id="_2-消息队列丢失消息" tabindex="-1">（2）消息队列丢失消息 <a class="header-anchor" href="#_2-消息队列丢失消息" aria-label="Permalink to &quot;（2）消息队列丢失消息&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111456584.png" alt="image-20220711145617551" style="zoom:67%;"><p>消息队列的消息可以放到内存中，或将内存中的消息转到硬盘（比如数据库）中，一般都是内存和硬盘中都存有消息。如果只是放在内存中，那么当机器重启了，消息就全部丢失了。如果是硬盘中，则可能存在一种极端情况，就是将内存中的数据转换到硬盘的期间中，消息队列出问题了，未能将消息持久化到硬盘。</p><p><strong>解决方案</strong></p><ul><li>创建 <code>Queue</code> 的时候将其设置为持久化。</li><li>发送消息的时候将消息的 <code>deliveryMode</code> 设置为 2 。</li><li>开启生产者 <code>confirm</code> 模式，可以重试发送消息。</li></ul><h3 id="_3-消费者丢失消息" tabindex="-1">（3）消费者丢失消息 <a class="header-anchor" href="#_3-消费者丢失消息" aria-label="Permalink to &quot;（3）消费者丢失消息&quot;">​</a></h3><p><img src="" alt="图片">消费者丢失消息</p><p>消费者刚拿到数据，还没开始处理消息，结果进程因为异常退出了，消费者没有机会再次拿到消息。</p><p><strong>解决方案</strong></p><ul><li>关闭 RabbitMQ 的自动 <code>ack</code>，每次生产者将消息写入消息队列后，就自动回传一个 <code>ack</code> 给生产者。</li><li>消费者处理完消息再主动 <code>ack</code>，告诉消息队列我处理完了。</li></ul><p><strong>问题：</strong> 那这种主动 <code>ack</code> 有什么漏洞了？如果 主动 <code>ack</code> 的时候挂了，怎么办？</p><p>则可能会被再次消费，这个时候就需要幂等处理了。</p><p><strong>问题：</strong> 如果这条消息一直被重复消费怎么办？</p><p>则需要有加上重试次数的监测，如果超过一定次数则将消息丢失，记录到异常表或发送异常通知给值班人员。</p><h3 id="_4-rabbitmq-消息丢失总结" tabindex="-1">（4）RabbitMQ 消息丢失总结 <a class="header-anchor" href="#_4-rabbitmq-消息丢失总结" aria-label="Permalink to &quot;（4）RabbitMQ 消息丢失总结&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111455315.png" alt="image-20220711145559276" style="zoom:67%;"><h3 id="_5-kafka-消息丢失" tabindex="-1">（5）Kafka 消息丢失 <a class="header-anchor" href="#_5-kafka-消息丢失" aria-label="Permalink to &quot;（5）Kafka 消息丢失&quot;">​</a></h3><p><strong>场景：</strong><code>Kafka</code> 的某个 broker（节点）宕机了，重新选举 leader （写入的节点）。如果 leader 挂了，follower 还有些数据未同步完，则 follower 成为 leader 后，消息队列会丢失一部分数据。</p><p><strong>解决方案</strong></p><ul><li>给 topic 设置 <code>replication.factor</code> 参数，值必须大于 1，要求每个 partition 必须有至少 2 个副本。</li><li>给 kafka 服务端设置 <code>min.insyc.replicas</code> 必须大于 1，表示一个 leader 至少一个 follower 还跟自己保持联系。</li></ul><h2 id="_3-消息队列的坑之消息乱序" tabindex="-1">3. 消息队列的坑之消息乱序 <a class="header-anchor" href="#_3-消息队列的坑之消息乱序" aria-label="Permalink to &quot;3. 消息队列的坑之消息乱序&quot;">​</a></h2><blockquote><p><code>坑:</code> 用户先下单成功，然后取消订单，如果顺序颠倒，则最后数据库里面会有一条下单成功的订单。</p></blockquote><p><strong>RabbitMQ 场景：</strong></p><ul><li>生产者向消息队列按照顺序发送了 2 条消息，消息1：增加数据 A，消息2：删除数据 A。</li><li>期望结果：数据 A 被删除。</li><li>但是如果有两个消费者，消费顺序是：消息2、消息 1。则最后结果是增加了数据 A。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111455774.png" alt="image-20220711145542731" style="zoom:67%;"><p><strong>RabbitMQ 解决方案：</strong></p><ul><li>将 Queue 进行拆分，创建多个内存 Queue，消息 1 和 消息 2 进入同一个 Queue。</li><li>创建多个消费者，每一个消费者对应一个 Queue。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111455748.png" alt="image-20220711145518709" style="zoom:67%;"><p><strong>Kafka 场景：</strong></p><ul><li>创建了 topic，有 3 个 partition。</li><li>创建一条订单记录，订单 id 作为 key，订单相关的消息都丢到同一个 partition 中，同一个生产者创建的消息，顺序是正确的。</li><li>为了快速消费消息，会创建多个消费者去处理消息，而为了提高效率，每个消费者可能会创建多个线程来并行的去拿消息及处理消息，处理消息的顺序可能就乱序了。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111455236.png" alt="image-20220711145501182" style="zoom:50%;"><p><strong>Kafka 解决方案：</strong></p><ul><li>解决方案和 RabbitMQ 类似，利用多个 内存 Queue，每个线程消费 1个 Queue。</li><li>具有相同 key 的消息 进同一个 Queue。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111454095.png" alt="image-20220711145445040" style="zoom:50%;"><h2 id="_4-消息队列的坑之消息积压" tabindex="-1">4. 消息队列的坑之消息积压 <a class="header-anchor" href="#_4-消息队列的坑之消息积压" aria-label="Permalink to &quot;4. 消息队列的坑之消息积压&quot;">​</a></h2><p>消息积压：消息队列里面有很多消息来不及消费。</p><p><strong>场景 1：</strong> 消费端出了问题，比如消费者都挂了，没有消费者来消费了，导致消息在队列里面不断积压。</p><p><strong>场景 2：</strong> 消费端出了问题，比如消费者消费的速度太慢了，导致消息不断积压。</p><blockquote><p>坑：比如线上正在做订单活动，下单全部走消息队列，如果消息不断积压，订单都没有下单成功，那么将会损失很多交易。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111454719.png" alt="image-20220711145415686" style="zoom:67%;"><p>解决方案：<strong>解铃还须系铃人</strong></p><ul><li>修复代码层面消费者的问题，确保后续消费速度恢复或尽可能加快消费的速度。</li><li>停掉现有的消费者。</li><li>临时建立好原先 5 倍的 Queue 数量。</li><li>临时建立好原先 5 倍数量的 消费者。</li><li>将堆积的消息全部转入临时的 Queue，消费者来消费这些 Queue。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111454467.png" alt="image-20220711145427427" style="zoom:67%;"><h2 id="_5-消息队列的坑之消息过期失效" tabindex="-1">5. 消息队列的坑之消息过期失效 <a class="header-anchor" href="#_5-消息队列的坑之消息过期失效" aria-label="Permalink to &quot;5. 消息队列的坑之消息过期失效&quot;">​</a></h2><blockquote><p><code>坑：</code>RabbitMQ 可以设置过期时间，如果消息超过一定的时间还没有被消费，则会被 RabbitMQ 给清理掉。消息就丢失了。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111453926.png" alt="image-20220711145343892" style="zoom:67%;"><p>解决方案：</p><ul><li>准备好批量重导的程序</li><li>手动将消息闲时批量重导</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111453562.png" alt="image-20220711145355531" style="zoom:67%;"><h2 id="_6-消息队列的坑之队列写满" tabindex="-1">6. 消息队列的坑之队列写满 <a class="header-anchor" href="#_6-消息队列的坑之队列写满" aria-label="Permalink to &quot;6. 消息队列的坑之队列写满&quot;">​</a></h2><blockquote><p><code>坑：</code>当消息队列因消息积压导致的队列快写满，所以不能接收更多的消息了。生产者生产的消息将会被丢弃。</p></blockquote><p>解决方案：</p><ul><li>判断哪些是无用的消息，RabbitMQ 可以进行 <code>Purge Message</code> 操作。</li><li>如果是有用的消息，则需要将消息快速消费，将消息里面的内容转存到数据库。</li><li>准备好程序将转存在数据库中的消息再次重导到消息队列。</li><li>闲时重导消息到消息队列。</li></ul><h1 id="二、分布式缓存的坑" tabindex="-1">二、分布式缓存的坑 <a class="header-anchor" href="#二、分布式缓存的坑" aria-label="Permalink to &quot;二、分布式缓存的坑&quot;">​</a></h1><p>在高频访问数据库的场景中，我们会在业务层和数据层之间加入一套缓存机制，来分担数据库的访问压力，毕竟访问磁盘 I/O 的速度是很慢的。比如利用缓存来查数据，可能5ms就能搞定，而去查数据库可能需要 50 ms，差了一个数量级。而在高并发的情况下，数据库还有可能对数据进行加锁，导致访问数据库的速度更慢。</p><p>分布式缓存我们用的最多的就是 Redis了，它可以提供分布式缓存服务。</p><h2 id="_1-redis-数据丢失的坑" tabindex="-1">1. Redis 数据丢失的坑 <a class="header-anchor" href="#_1-redis-数据丢失的坑" aria-label="Permalink to &quot;1. Redis 数据丢失的坑&quot;">​</a></h2><h3 id="哨兵机制" tabindex="-1">哨兵机制 <a class="header-anchor" href="#哨兵机制" aria-label="Permalink to &quot;哨兵机制&quot;">​</a></h3><p>Redis 可以实现利用<code>哨兵机制</code>实现集群的高可用。那什么十哨兵机制呢？</p><ul><li>英文名：<code>sentinel</code>，中文名：<code>哨兵</code>。</li><li>集群监控：负责主副进程的正常工作。</li><li>消息通知：负责将故障信息报警给运维人员。</li><li>故障转移：负责将主节点转移到备用节点上。</li><li>配置中心：通知客户端更新主节点地址。</li><li>分布式：有多个哨兵分布在每个主备节点上，互相协同工作。</li><li>分布式选举：需要大部分哨兵都同意，才能进行主备切换。</li><li>高可用：即使部分哨兵节点宕机了，哨兵集群还是能正常工作。</li></ul><blockquote><p><code>坑：</code> 当主节点发生故障时，需要进行主备切换，可能会导致数据丢失。</p></blockquote><h3 id="异步复制数据导致的数据丢失" tabindex="-1">异步复制数据导致的数据丢失 <a class="header-anchor" href="#异步复制数据导致的数据丢失" aria-label="Permalink to &quot;异步复制数据导致的数据丢失&quot;">​</a></h3><p>主节点异步同步数据给备用节点的过程中，主节点宕机了，导致有部分数据未同步到备用节点。而这个从节点又被选举为主节点，这个时候就有部分数据丢失了。</p><h3 id="脑裂导致的数据丢失" tabindex="-1">脑裂导致的数据丢失 <a class="header-anchor" href="#脑裂导致的数据丢失" aria-label="Permalink to &quot;脑裂导致的数据丢失&quot;">​</a></h3><p>主节点所在机器脱离了集群网络，实际上自身还是运行着的。但哨兵选举出了备用节点作为主节点，这个时候就有两个主节点都在运行，相当于两个大脑在指挥这个集群干活，但到底听谁的呢？这个就是脑裂。</p><p>那怎么脑裂怎么会导致数据丢失呢？如果发生脑裂后，客户端还没来得及切换到新的主节点，连的还是第一个主节点，那么有些数据还是写入到了第一个主节点里面，新的主节点没有这些数据。那等到第一个主节点恢复后，会被作为备用节点连到集群环境，而且自身数据会被清空，重新从新的主节点复制数据。而新的主节点因没有客户端之前写入的数据，所以导致数据丢失了一部分。</p><h3 id="避坑指南-1" tabindex="-1">避坑指南 <a class="header-anchor" href="#避坑指南-1" aria-label="Permalink to &quot;避坑指南&quot;">​</a></h3><ul><li>配置 min-slaves-to-write 1，表示至少有一个备用节点。</li><li>配置 min-slaves-max-lag 10，表示数据复制和同步的延迟不能超过 10 秒。最多丢失 10 秒的数据</li></ul><p>注意：<code>缓存雪崩</code>、<code>缓存穿透</code>、<code>缓存击穿</code>并不是分布式所独有的，单机的时候也会出现。所以不在分布式的坑之列。</p><h1 id="三、分库分表的坑" tabindex="-1">三、分库分表的坑 <a class="header-anchor" href="#三、分库分表的坑" aria-label="Permalink to &quot;三、分库分表的坑&quot;">​</a></h1><h2 id="_1-分库分表的坑之扩容" tabindex="-1">1.分库分表的坑之扩容 <a class="header-anchor" href="#_1-分库分表的坑之扩容" aria-label="Permalink to &quot;1.分库分表的坑之扩容&quot;">​</a></h2><h4 id="分库、分表、垂直拆分和水平拆分" tabindex="-1">分库、分表、垂直拆分和水平拆分 <a class="header-anchor" href="#分库、分表、垂直拆分和水平拆分" aria-label="Permalink to &quot;分库、分表、垂直拆分和水平拆分&quot;">​</a></h4><ul><li><p><strong>分库：</strong> 因一个数据库支持的最高并发访问数是有限的，可以将一个数据库的数据拆分到多个库中，来增加最高并发访问数。</p></li><li><p><strong>分表：</strong> 因一张表的数据量太大，用索引来查询数据都搞不定了，所以可以将一张表的数据拆分到多张表，查询时，只用查拆分后的某一张表，SQL 语句的查询性能得到提升。</p></li><li><p>分库分表优势：分库分表后，承受的并发增加了多倍；磁盘使用率大大降低；单表数据量减少，SQL 执行效率明显提升。</p></li><li><p><strong>水平拆分：</strong> 把一个表的数据拆分到多个数据库，每个数据库中的表结构不变。用多个库抗更高的并发。比如订单表每个月有500万条数据累计，每个月都可以进行水平拆分，将上个月的数据放到另外一个数据库。</p></li><li><p><strong>垂直拆分：</strong> 把一个有很多字段的表，拆分成多张表到同一个库或多个库上面。高频访问字段放到一张表，低频访问的字段放到另外一张表。利用数据库缓存来缓存高频访问的行数据。比如将一张很多字段的订单表拆分成几张表分别存不同的字段（可以有冗余字段）。</p></li><li><p><strong>分库、分表的方式：</strong></p></li><li><ul><li>根据租户来分库、分表。</li><li>利用时间范围来分库、分表。</li><li>利用 ID 取模来分库、分表。</li></ul></li></ul><blockquote><p><code>坑：</code>分库分表是一个运维层面需要做的事情，有时会采取凌晨宕机开始升级。可能熬夜到天亮，结果升级失败，则需要回滚，其实对技术团队都是一种煎熬。</p></blockquote><h4 id="怎么做成自动的来节省分库分表的时间" tabindex="-1">怎么做成自动的来节省分库分表的时间？ <a class="header-anchor" href="#怎么做成自动的来节省分库分表的时间" aria-label="Permalink to &quot;怎么做成自动的来节省分库分表的时间？&quot;">​</a></h4><ul><li>双写迁移方案：迁移时，新数据的增删改操作在新库和老库都做一遍。</li><li>使用分库分表工具 Sharding-jdbc 来完成分库分表的累活。</li><li>使用程序来对比两个库的数据是否一致，直到数据一致。</li></ul><blockquote><p><code>坑:</code> 分库分表看似光鲜亮丽，但分库分表会引入什么新的问题呢？</p></blockquote><h4 id="垂直拆分带来的问题" tabindex="-1">垂直拆分带来的问题 <a class="header-anchor" href="#垂直拆分带来的问题" aria-label="Permalink to &quot;垂直拆分带来的问题&quot;">​</a></h4><ul><li>依然存在单表数据量过大的问题。</li><li>部分表无法关联查询，只能通过接口聚合方式解决，提升了开发的复杂度。</li><li>分布式事处理复杂。</li></ul><h4 id="水平拆分带来的问题" tabindex="-1">水平拆分带来的问题 <a class="header-anchor" href="#水平拆分带来的问题" aria-label="Permalink to &quot;水平拆分带来的问题&quot;">​</a></h4><ul><li>跨库的关联查询性能差。</li><li>数据多次扩容和维护量大。</li><li>跨分片的事务一致性难以保证。</li></ul><h2 id="_2-分库分表的坑之唯一-id" tabindex="-1">2.分库分表的坑之唯一 ID <a class="header-anchor" href="#_2-分库分表的坑之唯一-id" aria-label="Permalink to &quot;2.分库分表的坑之唯一 ID&quot;">​</a></h2><h3 id="为什么分库分表需要唯一-id" tabindex="-1">为什么分库分表需要唯一 ID <a class="header-anchor" href="#为什么分库分表需要唯一-id" aria-label="Permalink to &quot;为什么分库分表需要唯一 ID&quot;">​</a></h3><ul><li>如果要做分库分表，则必须得考虑表主键 ID 是全局唯一的，比如有一张订单表，被分到 A 库和 B 库。如果 两张订单表都是从 1 开始递增，那查询订单数据时就错乱了，很多订单 ID 都是重复的，而这些订单其实不是同一个订单。</li><li>分库的一个期望结果就是将访问数据的次数分摊到其他库，有些场景是需要均匀分摊的，那么数据插入到多个数据库的时候就需要交替生成唯一的 ID 来保证请求均匀分摊到所有数据库。</li></ul><blockquote><p><code>坑:</code> 唯一 ID 的生成方式有 n 种，各有各的用途，别用错了。</p></blockquote><h3 id="生成唯一-id-的原则" tabindex="-1">生成唯一 ID 的原则 <a class="header-anchor" href="#生成唯一-id-的原则" aria-label="Permalink to &quot;生成唯一 ID 的原则&quot;">​</a></h3><ul><li>全局唯一性</li><li>趋势递增</li><li>单调递增</li><li>信息安全</li></ul><h3 id="生成唯一-id-的几种方式" tabindex="-1">生成唯一 ID 的几种方式 <a class="header-anchor" href="#生成唯一-id-的几种方式" aria-label="Permalink to &quot;生成唯一 ID 的几种方式&quot;">​</a></h3><ul><li><p>数据库自增 ID。每个数据库每增加一条记录，自己的 ID 自增 1。</p></li><li><ul><li>多个库的 ID 可能重复，这个方案可以直接否掉了，不适合分库分表后的 ID 生成。</li><li>信息不安全</li><li>缺点</li></ul></li><li><p>适用 <code>UUID</code> 唯一 ID。</p></li><li><ul><li>UUID 太长、占用空间大。</li><li>不具有有序性，作为主键时，在写入数据时，不能产生有顺序的 append 操作，只能进行 insert 操作，导致读取整个 <code>B+</code> 树节点到内存，插入记录后将整个节点写回磁盘，当记录占用空间很大的时候，性能很差。</li><li>缺点</li></ul></li><li><p>获取系统当前时间作为唯一 ID。</p></li><li><ul><li>高并发时，1 ms内可能有多个相同的 ID。</li><li>信息不安全</li><li>缺点</li></ul></li><li><p>Twitter 的 <code>snowflake</code>（雪花算法）：Twitter 开源的分布式 id 生成算法，64 位的 long 型的 id，分为 4 部分</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111453589.png" alt="image-20220711145304554" style="zoom:67%;"></li><li><p>基本原理和优缺点：</p></li><li><ul><li><p>1 bit：不用，统一为 0</p></li><li><p>41 bits：毫秒时间戳，可以表示 69 年的时间。</p></li><li><p>10 bits：5 bits 代表机房 id，5 个 bits 代表机器 id。最多代表 32 个机房，每个机房最多代表 32 台机器。</p></li><li><p>12 bits：同一毫秒内的 id，最多 4096 个不同 id，自增模式。</p></li><li><p>优点：</p></li><li><ul><li>毫秒数在高位，自增序列在低位，整个ID都是趋势递增的。</li><li>不依赖数据库等第三方系统，以服务的方式部署，稳定性更高，生成ID的性能也是非常高的。</li><li>可以根据自身业务特性分配bit位，非常灵活。</li></ul></li><li><p>缺点：</p></li><li><ul><li>强依赖机器时钟，如果机器上时钟回拨（可以搜索 <strong>2017 年闰秒 7:59:60</strong>），会导致发号重复或者服务会处于不可用状态。</li></ul></li></ul></li><li><p>百度的 <code>UIDGenerator</code> 算法。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111452681.png" alt="image-20220711145246635" style="zoom:67%;"></li><li><ul><li>基于 Snowflake 的优化算法。</li><li>借用未来时间和双 Buffer 来解决时间回拨与生成性能等问题，同时结合 MySQL 进行 ID 分配。</li><li>优点：解决了时间回拨和生成性能问题。</li><li>缺点：依赖 MySQL 数据库。</li></ul></li><li><p>美团的 <code>Leaf-Snowflake</code> 算法。</p></li><li><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111452340.png" alt="image-20220711145229270" style="zoom:67%;"></li><li><ul><li><p>获取 id 是通过代理服务访问数据库获取一批 id（号段）。</p></li><li><p>双缓冲：当前一批的 id 使用 10%时，再访问数据库获取新的一批 id 缓存起来，等上批的 id 用完后直接用。</p></li><li><p>优点：</p></li><li><ul><li>Leaf服务可以很方便的线性扩展，性能完全能够支撑大多数业务场景。</li><li>ID号码是趋势递增的8byte的64位数字，满足上述数据库存储的主键要求。</li><li>容灾性高：Leaf服务内部有号段缓存，即使DB宕机，短时间内Leaf仍能正常对外提供服务。</li><li>可以自定义max_id的大小，非常方便业务从原有的ID方式上迁移过来。</li><li>即使DB宕机，Leaf仍能持续发号一段时间。</li><li>偶尔的网络抖动不会影响下个号段的更新。</li></ul></li><li><p>缺点：</p></li><li><ul><li>ID号码不够随机，能够泄露发号数量的信息，不太安全。</li></ul></li></ul></li></ul><p>怎么选择：一般自己的内部系统，雪花算法足够，如果还要更加安全可靠，可以选择百度或美团的生成唯一 ID 的方案。</p><h1 id="四、分布式事务的坑" tabindex="-1">四、分布式事务的坑 <a class="header-anchor" href="#四、分布式事务的坑" aria-label="Permalink to &quot;四、分布式事务的坑&quot;">​</a></h1><h3 id="怎么理解事务" tabindex="-1">怎么理解事务？ <a class="header-anchor" href="#怎么理解事务" aria-label="Permalink to &quot;怎么理解事务？&quot;">​</a></h3><ul><li>事务可以简单理解为要么这件事情全部做完，要么这件事情一点都没做，跟没发生一样。</li><li>在分布式的世界中，存在着各个服务之间相互调用，链路可能很长，如果有任何一方执行出错，则需要回滚涉及到的其他服务的相关操作。比如订单服务下单成功，然后调用营销中心发券接口发了一张代金券，但是微信支付扣款失败，则需要退回发的那张券，且需要将订单状态改为异常订单。</li></ul><blockquote><p><code>坑</code>：如何保证分布式中的事务正确执行，是个大难题。</p></blockquote><h3 id="分布式事务的几种主要方式" tabindex="-1">分布式事务的几种主要方式 <a class="header-anchor" href="#分布式事务的几种主要方式" aria-label="Permalink to &quot;分布式事务的几种主要方式&quot;">​</a></h3><ul><li>XA 方案（两阶段提交方案）</li><li>TCC 方案（try、confirm、cancel）</li><li>SAGA 方案</li><li>可靠消息最终一致性方案</li><li>最大努力通知方案</li></ul><h3 id="xa-方案原理" tabindex="-1">XA 方案原理 <a class="header-anchor" href="#xa-方案原理" aria-label="Permalink to &quot;XA 方案原理&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111451833.png" alt="image-20220711145123793" style="zoom:67%;"><ul><li>事务管理器负责协调多个数据库的事务，先问问各个数据库准备好了吗？如果准备好了，则在数据库执行操作，如果任一数据库没有准备，则回滚事务。</li><li>适合单体应用，不适合微服务架构。因为每个服务只能访问自己的数据库，不允许交叉访问其他微服务的数据库。</li></ul><h3 id="tcc-方案" tabindex="-1">TCC 方案 <a class="header-anchor" href="#tcc-方案" aria-label="Permalink to &quot;TCC 方案&quot;">​</a></h3><ul><li>Try 阶段：对各个服务的资源做检测以及对资源进行锁定或者预留。</li><li>Confirm 阶段：各个服务中执行实际的操作。</li><li>Cancel 阶段：如果任何一个服务的业务方法执行出错，需要将之前操作成功的步骤进行回滚。</li></ul><p>应用场景：</p><ul><li>跟支付、交易打交道，必须保证资金正确的场景。</li><li>对于一致性要求高。</li></ul><p>缺点：</p><ul><li>但因为要写很多补偿逻辑的代码，且不易维护，所以其他场景建议不要这么做。</li></ul><h3 id="sega-方案" tabindex="-1">Sega 方案 <a class="header-anchor" href="#sega-方案" aria-label="Permalink to &quot;Sega 方案&quot;">​</a></h3><p>基本原理：</p><ul><li>业务流程中的每个步骤若有一个失败了，则补偿前面操作成功的步骤。</li></ul><p>适用场景：</p><ul><li>业务流程长、业务流程多。</li><li>参与者包含其他公司或遗留系统服务。</li></ul><p>优势：</p><ul><li>第一个阶段提交本地事务、无锁、高性能。</li><li>参与者可异步执行、高吞吐。</li><li>补偿服务易于实现。</li></ul><p>缺点：</p><ul><li>不保证事务的隔离性。</li></ul><h3 id="可靠消息一致性方案" tabindex="-1">可靠消息一致性方案 <a class="header-anchor" href="#可靠消息一致性方案" aria-label="Permalink to &quot;可靠消息一致性方案&quot;">​</a></h3><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111451685.png" alt="image-20220711145139643" style="zoom:67%;"><p>基本原理：</p><ul><li>利用消息中间件 <code>RocketMQ</code> 来实现消息事务。</li><li>第一步：A 系统发送一个消息到 MQ，MQ将消息状态标记为 <code>prepared</code>（预备状态，半消息），该消息无法被订阅。</li><li>第二步：MQ 响应 A 系统，告诉 A 系统已经接收到消息了。</li><li>第三步：A 系统执行本地事务。</li><li>第四步：若 A 系统执行本地事务成功，将 <code>prepared</code> 消息改为 <code>commit</code>（提交事务消息），B 系统就可以订阅到消息了。</li><li>第五步：MQ 也会定时轮询所有 <code>prepared</code>的消息，回调 A 系统，让 A 系统告诉 MQ 本地事务处理得怎么样了，是继续等待还是回滚。</li><li>第六步：A 系统检查本地事务的执行结果。</li><li>第七步：若 A 系统执行本地事务失败，则 MQ 收到 <code>Rollback</code> 信号，丢弃消息。若执行本地事务成功，则 MQ 收到 <code>Commit</code> 信号。</li><li>B 系统收到消息后，开始执行本地事务，如果执行失败，则自动不断重试直到成功。或 B 系统采取回滚的方式，同时要通过其他方式通知 A 系统也进行回滚。</li><li>B 系统需要保证幂等性。</li></ul><h3 id="最大努力通知方案" tabindex="-1">最大努力通知方案 <a class="header-anchor" href="#最大努力通知方案" aria-label="Permalink to &quot;最大努力通知方案&quot;">​</a></h3><p>基本原理：</p><ul><li>系统 A 本地事务执行完之后，发送消息到 MQ。</li><li>MQ 将消息持久化。</li><li>系统 B 如果执行本地事务失败，则<code>最大努力服务</code>会定时尝试重新调用系统 B，尽自己最大的努力让系统 B 重试，重试多次后，还是不行就只能放弃了。转到开发人员去排查以及后续人工补偿。</li></ul><h3 id="几种方案如何选择" tabindex="-1">几种方案如何选择 <a class="header-anchor" href="#几种方案如何选择" aria-label="Permalink to &quot;几种方案如何选择&quot;">​</a></h3><ul><li>跟支付、交易打交道，优先 TCC。</li><li>大型系统，但要求不那么严格，考虑 消息事务或 SAGA 方案。</li><li>单体应用，建议 XA 两阶段提交就可以了。</li><li>最大努力通知方案建议都加上，毕竟不可能一出问题就交给开发排查，先重试几次看能不能成功。</li></ul><h1 id="分布式算法" tabindex="-1">分布式算法 <a class="header-anchor" href="#分布式算法" aria-label="Permalink to &quot;分布式算法&quot;">​</a></h1><h2 id="前言-1" tabindex="-1">前言 <a class="header-anchor" href="#前言-1" aria-label="Permalink to &quot;前言&quot;">​</a></h2><blockquote><p>《三国杀》是一款热门的卡牌游戏，结合中国三国时期背景，以身份为线索，以卡牌为形式，益智休闲，老少皆宜。</p></blockquote><p>东汉末年，袁绍作为盟主，汇合了十八路诸侯一起攻打董卓。</p><p>在讲解之前，我们先聊下分布式协议和算法整体脉络。</p><p>现在很多开发同学对分布式的组件怎么使用都有一定经验，也知道 <code>CAP</code> 理论和 <code>BASE</code> 理论的大致含义。<code>但认真去看分布式算法的真的很少，原因有三</code>：</p><ul><li>担心算法过于复杂，所以花的时间很少。</li><li>网上的资料能用大白话将分布式算法讲清楚的比较少。</li><li>学习分布式算法没有一条清晰的路线。</li></ul><p>我会在后续的文章中用故事、大白话的方式来讲解分布式算法的原理，以及学习路线到底是怎么样的。</p><h2 id="学习路线" tabindex="-1">学习路线 <a class="header-anchor" href="#学习路线" aria-label="Permalink to &quot;学习路线&quot;">​</a></h2><p>学习分布式协议和算法的路线可以是先学习四大基础理论，作为地基，再学习分布式协议和算法，就像是在地基上建房子。地基打好了，才能建更稳固的高楼大厦。</p><h3 id="四大基础理论" tabindex="-1">四大基础理论 <a class="header-anchor" href="#四大基础理论" aria-label="Permalink to &quot;四大基础理论&quot;">​</a></h3><ul><li>拜占庭将军问题</li><li>CAP 理论</li><li>ACID 理论</li><li>BASE 理论</li></ul><h3 id="八大分布式协议和算法" tabindex="-1">八大分布式协议和算法 <a class="header-anchor" href="#八大分布式协议和算法" aria-label="Permalink to &quot;八大分布式协议和算法&quot;">​</a></h3><ul><li>Paxos 算法</li><li>Raft 算法</li><li>一致性 Hash 算法</li><li>Gossip 协议算法</li><li>Quorum NWR 算法</li><li>FBFT 算法</li><li>POW 算法</li><li>ZAB 协议</li></ul><p>因篇幅原因，本篇只涉及拜占庭将军问题。</p><h2 id="拜占庭将军问题" tabindex="-1">拜占庭将军问题 <a class="header-anchor" href="#拜占庭将军问题" aria-label="Permalink to &quot;拜占庭将军问题&quot;">​</a></h2><p>大家可能听过拜占庭将军问题。它是由莱斯利·兰伯特提出的点对点通信中的基本问题，</p><p><code>拜占庭</code>位于如今的土耳其的<code>伊斯坦布尔</code>，是<code>东罗马帝国</code>的首都。由于当时拜占庭罗马帝国国土辽阔，为了达到防御目的，每个军队都分隔很远，将军与将军之间只能靠信差传消息。在战争的时候，拜占庭军队内所有将军和副官必须达成一致的共识，决定是否有赢的机会才去攻打敌人的阵营。但是，在军队内有可能存有叛徒和敌军的间谍，这个就是拜占庭容错问题。</p><p>实际上拜占庭问题是分布式领域最复杂的一个容错模型，一旦理解它，就能掌握分布式共识问题的解决思路，还能帮助大家理解常用的共识算法，也可以帮助我们在工作中选择合适的算法，或者设计合适的算法。</p><p>为什么第一个基础理论是拜占庭将军问题？</p><p><code>因为它很好地抽象出了分布式系统面临的共识问题。</code> 上面提到的 8 种分布式算法中有 5 种跟拜占庭问题相关，可以说弄懂拜占庭问题对后面学习其他算法就会容易很多。</p><p>下面我用三国杀游戏中的身份牌来讲解拜占庭将军问题。</p><h2 id="三国杀身份牌" tabindex="-1">三国杀身份牌 <a class="header-anchor" href="#三国杀身份牌" aria-label="Permalink to &quot;三国杀身份牌&quot;">​</a></h2><p>三国杀中主要有四种身份：主公、忠臣、反贼、内奸。每个游戏玩家都会获得一个身份牌。主公只有 1 个。忠臣 最多 2 个，反贼最多 4个，内奸最多一个。</p><h3 id="主公" tabindex="-1">主公 <a class="header-anchor" href="#主公" aria-label="Permalink to &quot;主公&quot;">​</a></h3><p><img src="https://mmbiz.qpic.cn/mmbiz_png/SfAHMuUxqJ3EponJUvecQg0gQ0ZmcpgzvQzwzO4uOtE540vfgBBhkwkCSIeqnKTqTXnTlibabFyleAv24WD7QeQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>获胜条件： 消灭所有反贼和内奸</p><p>技巧： 以自己生存为首要目标，分散反贼注意力。配合忠内剿灭反贼并判断谁是忠谁是内。</p><h3 id="忠臣" tabindex="-1">忠臣 <a class="header-anchor" href="#忠臣" aria-label="Permalink to &quot;忠臣&quot;">​</a></h3><p><img src="https://mmbiz.qpic.cn/mmbiz_png/SfAHMuUxqJ3EponJUvecQg0gQ0Zmcpgz38AxcDD5ukFyjMLVqZe3SbdBKRCW1hSA931Ljz5p1AWTwk8uOR0Fpg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>获胜条件：保护主公存活的前提下消灭所有反贼和内奸。</p><p>技巧：忠臣是主公的屏障，威慑反贼和内奸的天平。</p><h3 id="反贼" tabindex="-1">反贼 <a class="header-anchor" href="#反贼" aria-label="Permalink to &quot;反贼&quot;">​</a></h3><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/SfAHMuUxqJ3EponJUvecQg0gQ0ZmcpgzDT19k0tOshnDoicXndDj49olUa8t2ia6YMYILYU3ygSd2c4iaGePhBkRg/640?wx_fmt=jpeg&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>获胜条件：消灭主公即可获胜。</p><p>技巧： 反贼作为数量最多的身份，需要集中火力猛攻敌人弱点。正确的思路是获胜的关键。</p><h3 id="内奸" tabindex="-1">内奸 <a class="header-anchor" href="#内奸" aria-label="Permalink to &quot;内奸&quot;">​</a></h3><p><img src="https://mmbiz.qpic.cn/mmbiz_png/SfAHMuUxqJ3EponJUvecQg0gQ0ZmcpgzfjVqC7B8v6YicvSMDUVia65wY7MDwgh4JTUwueQQYpJR3Rbfg900EOLA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>获胜条件： 先消灭反贼和忠臣，最后与主公单挑成为最后唯一生还者。</p><p>技巧：正确的战术+ 冷静的头脑+ 运气。</p><h2 id="还原拜占庭问题" tabindex="-1">还原拜占庭问题 <a class="header-anchor" href="#还原拜占庭问题" aria-label="Permalink to &quot;还原拜占庭问题&quot;">​</a></h2><p>东汉末年，袁绍作为盟主，汇合了十八路诸侯一起攻打董卓。把董卓定为反贼，袁绍定为主公，另外有两个忠臣和一个内奸，就选这三个风云人物：曹操，刘备，孙坚（孙权的爸比），内奸扮演的角色是忠臣，主公和两个忠臣不知道内奸的身份，都当作忠臣对待了。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111634370.png" alt="image-20220711163435295" style="zoom:50%;"><p>董卓是非常强大的，拥有精良的西凉兵，麾下还有战神吕布。大家都知道三英战吕布的故事，吕布以一已之力对阵刘备、张飞、关羽三人。</p><p>要想干掉董卓，袁绍必须统一忠臣的作战计划，三位忠臣还不知道有什么其他花花肠子，有一个还是内奸。如果内奸暗通反贼董卓，给忠臣发送误导性的作战信息，该怎么办？另外假定这几个忠臣都是通过书信交流作战信息，如果书信被拦截了或书信里面的信息被替换了咋办？这些场景都可能扰乱作战计划，最后出现有的忠臣在进攻，有的忠臣撤退了。那么反贼就可以乘此机会发起进攻，逐一攻破。</p><p>袁绍本来就没有曹操的机智，那他如何让忠臣们达成共识，制定统一的作战计划呢？</p><p>上面的映射关系就是一个拜占庭将军问题的一个简化表述，袁绍现在面临的就是典型的共识问题。也就是在可能有误导信息的情况下，采用合适的通讯机制，让多个将军达成共识，制定一致性的作战计划。</p><h2 id="一方选择撤退" tabindex="-1">一方选择撤退 <a class="header-anchor" href="#一方选择撤退" aria-label="Permalink to &quot;一方选择撤退&quot;">​</a></h2><p>刘备、曹操、孙坚通过信使传递进攻或撤退的信息，然后进行协商，到底是进攻还是撤退。遵循少数服从多数，不允许弃权。</p><p>曹操疑心比较重，侦查了反贼的地形后，决定撤退。而刘备和孙坚决定进攻。</p><ul><li>刘备决定进攻，通过信使告诉曹操和孙坚进攻。</li><li>曹操决定撤退，通过信使告诉刘备和孙坚撤退。</li><li>孙坚决定进攻，通过信使告诉曹操和刘备进攻。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111634739.png" alt="image-20220711163454673" style="zoom:50%;"><p>曹操收到的信息：进攻 2 票，自己的一张撤退票，票数一比，进攻票：撤退票 = 2 : 1，按照上面的少数服从多数原则进行投票表决，曹操还是会进攻。那么三方的作战方案都是进攻，所以是一个一致性的作战方案。最后战胜了董卓。</p><h2 id="内奸登场-撤退" tabindex="-1">内奸登场-撤退 <a class="header-anchor" href="#内奸登场-撤退" aria-label="Permalink to &quot;内奸登场-撤退&quot;">​</a></h2><p>因为我们前期的设定，孙坚作为内奸，早已与反贼董卓私下沟通好了，不攻打董卓。</p><ul><li>刘备决定进攻，通过信使告诉曹操和孙坚进攻。</li><li>曹操决定撤退，通过信使告诉曹操和孙坚撤退。</li><li>孙坚决定撤退，通过信使告诉曹操和刘备撤退。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111635612.png" alt="image-20220711163506544" style="zoom:50%;"><p>刘备收到进攻和撤退各一票，而自己又选择撤退，所以刘备得到的票数是：进攻 : 撤退 = 1 : 2，遵从少数服从多数的原则，刘备选择最后选择撤退，那么三方的作战方案都是撤退，所以也是一个一致性的作战方案。</p><h2 id="内奸使诈-一进一退" tabindex="-1">内奸使诈-一进一退 <a class="header-anchor" href="#内奸使诈-一进一退" aria-label="Permalink to &quot;内奸使诈-一进一退&quot;">​</a></h2><p>内奸看了上述计划，发现忠臣都撤退了，并没有被消灭，就想通过使诈的方式来消灭其中一个忠臣。</p><ul><li>刘备决定进攻，通过信使告诉曹操和孙坚进攻。</li><li>曹操决定撤退，通过信使告诉刘备和孙坚撤退。</li><li>孙坚作为内奸使诈，通过信使告诉刘备进攻，告诉曹操撤退。</li></ul><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111635542.png" alt="image-20220711163518474" style="zoom:50%;"><p>那么结果是什么呢？</p><p>刘备的票数为进攻 2 票，撤退 1 票，曹操的票数为进攻 1 票，撤退 2 票。按照少数服从多数的原则，刘备最后会选择进攻，而曹操会选择撤退，孙坚作为内奸肯定不会进攻，刘备单独进攻反贼董卓，势单力薄，被董卓干掉了。</p><p>从这个场景中，我们看到内奸孙坚通过发送误导信息，非常容易地就干扰了刘备和曹操的作战计划，导致两位忠臣被逐一击破。这个现象就是二忠一判难题。那么主公袁绍该怎么解决这个问题？</p><h2 id="拜占庭问题解法" tabindex="-1">拜占庭问题解法 <a class="header-anchor" href="#拜占庭问题解法" aria-label="Permalink to &quot;拜占庭问题解法&quot;">​</a></h2><h3 id="解法原理" tabindex="-1">解法原理 <a class="header-anchor" href="#解法原理" aria-label="Permalink to &quot;解法原理&quot;">​</a></h3><p>就是将袁绍也参与进来进行投票，这样就增加了一位忠臣的数量。三个忠臣一个叛贼。然后 4 位将军做了一个约定，如果没有收到命令，则执行默认命令，比如撤退。另外约定流程来发送作战信息和如何执行作战指令。这个解法的关键点就是执行两轮作战信息协商。</p><p>我们来看下第一轮是怎么做的。</p><ul><li>第一步：先发送作战信息的将军我们把他称为指挥官（袁绍），另外的将军我们称作副官（刘备，曹操，孙坚）。</li><li>第二步：<strong>指挥官</strong>将他的作战信息发送给所有的副官。</li><li>第三步：每一位副官将从指挥官处收到的作战信息，作为自己的作战指令；假如没有收到指挥官的作战信息，将把默认的撤退作为作战指令。</li></ul><p>我们用图来演示：袁绍作为主公先发送作战信息，作战指令为进攻。然后曹操、刘备、孙坚收到进攻的作战指令。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111635349.png" alt="image-20220711163536287" style="zoom:50%;"><p>再来看下第二轮是怎么做的。</p><ul><li>第一轮指挥官（袁绍）已经发送指令了，现在就需要刘备、曹操、孙坚依次作为指挥官给其他两位副将发送作战信息。</li><li>然后这三位副将按照少数服从多数的原则，执行收到的作战指令。</li></ul><h3 id="孙坚使诈-两撤退" tabindex="-1">孙坚使诈 - 两撤退 <a class="header-anchor" href="#孙坚使诈-两撤退" aria-label="Permalink to &quot;孙坚使诈 - 两撤退&quot;">​</a></h3><p>如果孙坚使诈，比如给曹操和刘备都发送撤退信息，如下图所示。那么刘备和曹操收到的作战信息为 进攻 2票，撤退 1 票，按照少数服从多数的原则，最后刘备和曹操执行进攻，实现了作战计划的一致性，曹操和刘备联合作战击败了反贼董卓（即使孙坚没有参加作战。）</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111635167.png" alt="image-20220711163549093" style="zoom:50%;"><h3 id="孙坚使诈-一进一退" tabindex="-1">孙坚使诈 - 一进一退 <a class="header-anchor" href="#孙坚使诈-一进一退" aria-label="Permalink to &quot;孙坚使诈 - 一进一退&quot;">​</a></h3><p>假如孙坚使诈，给曹操发送撤退指令，给刘备发送进攻指令，那么刘备收到的作战信息是进攻 3票，肯定会发起进攻了，而曹操收到的作战信息是进攻 2 票，撤退 1 票，最后曹操还是会进攻，所以刘备和曹操还是联合作战击败了反贼董卓。</p><p>如此看来，引入了一位指挥官后，确实可以避免孙坚使诈，但如果是孙坚在第一轮作为指挥官，其他人作为副官呢？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111636216.png" alt="image-20220711163618145" style="zoom:50%;"><h3 id="孙坚作为指挥官" tabindex="-1">孙坚作为指挥官 <a class="header-anchor" href="#孙坚作为指挥官" aria-label="Permalink to &quot;孙坚作为指挥官&quot;">​</a></h3><p>第一轮孙坚向其中一个副官袁绍发送撤退指令，向另外两个副官曹操、刘备发送进攻指令。那么第一轮的结果如下图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111636605.png" alt="image-20220711163632539" style="zoom:50%;"><p>第二轮孙坚休息，其他副官按照孙坚发送的指令开始向另外的副官发送指令。</p><ul><li>曹操向刘备和袁绍发送进攻指令。</li><li>刘备向曹操和袁绍发送进攻指令。</li><li>袁绍向曹操和刘备发送撤退指令。</li></ul><p>如下图所示，最后曹操、刘备、袁绍收到的指令为进攻 2 票，撤退 1 票，按照少数服从多数原则，三个人都是发起进攻。执行了一致的作战计划，保证作战的胜利。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207111636952.png" alt="image-20220711163648879" style="zoom:50%;"><h3 id="小结" tabindex="-1">小结 <a class="header-anchor" href="#小结" aria-label="Permalink to &quot;小结&quot;">​</a></h3><p>通过上面的演示，我们知道了如何解决拜占庭将军问题。其实兰伯特在他的论文中也提到过如何解决。</p><blockquote><p>如果叛将人数为 m，将军数 n &gt;= 3m + 1，那么就可以解决拜占庭将军问题。</p><p>前提条件：叛将数 m 已知，需要进行 m + 1 轮的作战协商。</p></blockquote><p>这个公式，大家只需要记住就可以了，推到过程可以参考论文。</p><p>比如上述的攻打董卓问题，曹操、刘备、孙坚三个人当中，孙坚是叛将，他可以使诈，使作战计划不统一。必须增加一位忠臣袁绍来协商共识，才能达成一致性作战计划。</p><h2 id="拜占庭解法二-签名" tabindex="-1">拜占庭解法二-签名 <a class="header-anchor" href="#拜占庭解法二-签名" aria-label="Permalink to &quot;拜占庭解法二-签名&quot;">​</a></h2><p>那可以在不增加忠臣的情况下，解决拜占庭的二忠一判问题呢？</p><p>解法二就是通过签名消息。比如将军之间通过印章、虎符等信物进行通信。来保证这几个特征：</p><ul><li>签名无法伪造，对签名消息的内容进行任何更改都会被发现。</li><li>任何人都能验证将军签名的真伪。</li></ul><p>限于篇幅原因，签名的演示这里就不做展开了，感兴趣的@我，后续会加上。</p><h2 id="总结-1" tabindex="-1">总结 <a class="header-anchor" href="#总结-1" aria-label="Permalink to &quot;总结&quot;">​</a></h2><p>通过三国杀角色来讲解分布式中共识场景。那他们和分布式系统的映射关系是怎么样的呢？</p><ul><li>将军对应计算机节点。</li><li>忠臣的将军对应正常运行的计算机节点。</li><li>叛变的将军对应出现故障并会发送误导信息的计算机节点。</li><li>信使被杀对应通讯故障、信息丢失。</li><li>信使被间谍替换对应为通讯被恶意攻击、伪造信息或劫持通讯。</li></ul><p>可不要小瞧拜占庭问题，它可是分布式场景最复杂的的故障场景。比如在数字货币的区块链技术中就有用到这些知识点。而且必须使用拜占庭容错算法（也就是 Byzantine Fault Tolerance，<code>BFT</code>）。</p><p>拜占庭容错算法还有 <code>FBFT</code> 算法，<code>PoW</code> 算法，当然不会在这篇中去讲这些算法，后续再讲解。一口吃不了大胖子~</p><p>有了拜占庭容错算法，肯定有非拜占庭容错算法，顾名思义，就是没有发送误导信息的节点。<code>CFT</code> 算法就是解决分布式系统中存在故障，但不存在恶意节点的场景下的共识问题。简单来说就是可能因系统故障造成丢失消息或消息重复，但不存在错误消息、伪造消息。对应的算法有 <code>Paxos</code> 算法、<code>Raft</code> 算法、<code>ZAB</code> 协议。后续讲解~</p><p><strong>上面提到了 5 种算法，居然都是跟拜占庭问题有关，你说今天讲的拜占庭问题重要不重要？</strong></p><p><strong>这么多算法该如何选择？</strong></p><p>节点可信，选非拜占庭容错算法。否则就用拜占庭容错算法，如区块链中用到的 PoW 算法。</p><h1 id="最终一致性-1" tabindex="-1">最终一致性 <a class="header-anchor" href="#最终一致性-1" aria-label="Permalink to &quot;最终一致性&quot;">​</a></h1><p>提到分布式系统，就一定绕不开“一致性”，这次我们说说：最终一致性。</p><p>最终一致性是现在大部分高可用的分布式系统的核心思路。</p><p>估计有人对最终一致性不太熟，先来个简单介绍：</p><p>最终一致性指的是<strong>系统中的所有分散在不同节点的数据，经过一定时间后，最终能够达到符合业务定义的一致的状态。</strong></p><p>划重点：</p><ol><li>是数据一致性，不是事务一致性（ACID 是事务一致性）；</li><li>存在条件：多个节点/系统；</li><li>不一致可能是暂时的，最终要一致（鬼知道“最终”是多久）</li></ol><p>好，正文开始。</p><h2 id="最终一致性简述" tabindex="-1">最终一致性简述 <a class="header-anchor" href="#最终一致性简述" aria-label="Permalink to &quot;最终一致性简述&quot;">​</a></h2><p>最终一致性，一言以蔽之，过程松，结果紧。不管中间过程如何，结果必须符合业务需求，满足数据一致性的要求。</p><p>虽然，在实现中，有各种花样百出的方案，但是本质的思想都是一样的。我们现在就来忽略那些乱花迷眼的过程，仔细探讨下最终一致性的本质。</p><h2 id="最终一致性演示" tabindex="-1">最终一致性演示 <a class="header-anchor" href="#最终一致性演示" aria-label="Permalink to &quot;最终一致性演示&quot;">​</a></h2><p>在我刚入行不久的时候，能力有限，菜鸟一个，只能做一些小的功能模块。我印象最深的就是订单模块。</p><p>用户下单，订单模块收到下单请求后，执行对应的订单业务逻辑。最终，会把订单插入到订单表，并返回下单结果给用户。用户结算后，订单模块就会去根据支付情况去更新订单状态。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201721429.png" alt="image-20220720172125383" style="zoom:50%;"><p>就这点事儿，对我这个技术渣渣来说，开始也着实费了一番手脚，不过最终也成了熟手，维护起这个模块来也驾轻就熟了。</p><p>这种简单的小日子过了一阵子后，新任务来了！</p><p>产品经理告诉我，数据审计部门想要我维护的这个订单模块在订单完成后，能及时分发一份订单数据给他们。他们提供了一个接口，让我直接传数据给他们。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201721300.png" alt="image-20220720172139250" style="zoom:50%;"><p>两个问题出现了：</p><p><strong>问题 1：用户等待时间变长</strong></p><p>最简单的实现就是我更新完订单数据后，再顺序去调用数据审计部门给的接口，把订单数据传过去。</p><p>但是，从用户结算成功到更新订单状态这一系列的流程是同步的，假设这一系列流程所花费的时间是 n 毫秒。这就意味着，用户需要等待至少 n 毫秒。如果再加上传给数据审计部门的操作时间，假设为 m 毫秒，则整个用户就需要等待就 n+m 毫秒。</p><p>整个功能用户等待时间成本上升，体验下降。如下图：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/6nbNnibOq5KRnSAFAwhBvGMf8eibUwRhdolg3NTgCvWASvvFn4jzI2O3HURUfYrbt9OwO2RxYXDC2QaJ0PIYFLicw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p><strong>问题 2：部分成功，部分失败</strong></p><p>引入新的接口后，某些时候调用这个接口可能会失败，比如网络问题啊，验证问题啊，接口服务失败啊，很多原因。那么问题来了，新接口失败的时候怎么处理？</p><p>如果订单更新成功，传给数据审计部门的时候失败了，这种情况会让订单模块的后续处理变得很尴尬。</p><p>首先你不可能返回给客户端说你这次结算失败了，请求就没失败，你凭什么说人家失败了？其次，你又不能说这次业务上就是成功的，因为数据审计其实还挺重要的，它是业务逻辑的重要组成部分。</p><p>真是进退两难。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/6nbNnibOq5KRnSAFAwhBvGMf8eibUwRhdomicZtpLs5QafwS7GnLicWZs3kRaQSiaAKA9IJtRKfKPcWf185MIfNIzfg/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>这两个问题的解决方案其中之一就是<strong>最终一致性</strong>。</p><p>我们以前谈到过 CAP，知道如果牺牲一定的一致性就可以保证分区容错性和可用性。而最终一致性则是不能保证同时让所有的数据当时都符合业务需求，但是我们能保证任何时候服务在内部出现问题的时候都是可对外服务的。</p><p>四哥我平时喜欢玩游戏，那我们就用一个淘宝买 Switch 的例子，来解释最终一致性：</p><p>如果你想在淘宝同时买一个 Switch 的数字版游戏和一台 Switch，那么你付完钱后，你就可以立刻得到数字版的游戏，但是，对于那台购买的 Switch，你就要等几天，等到快递投递到家才可以拿到。</p><p>来梳理下这个例子的细节：</p><ul><li>首先淘宝上肯定得有个对顾客售卖 Switch 和数字游戏的商家去接受我们下的订单，并给你一个单号。</li><li>你得到了一个数字版游戏，但是没拿到 Switch。</li><li>你不知道这个商家背后 Switch 是怎么给你准备的，是不是中间他没货了还得跑别的商家串货，又或者没货等了两天才发给你(延迟发货可以给出别的理由，不再赘述)。这些不重要，重要的是你明确对方接单了他就要完成这笔单子。</li><li>你下单成功之后，你就有了保障，你最终会拿到你的 Switch，只是你可能不太肯定什么时候收到。</li></ul><p>过了几天，你终于收到货了，恩，恭喜你成功入坑 Switch。</p><p>上面的例子就是我们说的最终一致性。但是，这里有个非常非常重要的东西还没有凸显出来，即到底是什么样的原因在驱使我们使用最终一致性？</p><p>答案就是<strong>数据的分发</strong>。</p><h2 id="为什么需要最终一致性的情况" tabindex="-1">为什么需要最终一致性的情况 <a class="header-anchor" href="#为什么需要最终一致性的情况" aria-label="Permalink to &quot;为什么需要最终一致性的情况&quot;">​</a></h2><p>因为我们需要把数据分发到不同的地方上去，而由于分发数据到不同的地方，就会导致，可能中间分发过程中出现分发成功或者失败的不一致情况，就需要最终一致性这种思路来处理这些情况。</p><p>恩，分发数据……OK，你想到了吧？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201723854.png" alt="image-20220720172336784" style="zoom:50%;"><p>没错，通过 MQ 分发消息就可以处理分发数据的情况，而这正是最终一致性最常用的实现手段。</p><p>我们把要分发的数据打包成消息，再发送给 MQ 中间件。中间件会广播这些数据给所有想要收到这些消息的服务。这些收到消息的服务就根据自己的业务情况对数据进行独立的处理。</p><p>回到我们订单模块的那个例子，我们可以采用两种方式使用最终一致性。</p><ol><li>先插入数据库，后发消息给数据审计</li></ol><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201723048.png" alt="image-20220720172349991" style="zoom:80%;"><p>这个方式，订单模块先更新订单状态。然后，把订单数据打包成消息发送到 MQ 中，订单模块的任务就结束了。剩下的任务就是由数据审计部门根据自己的业务，从 MQ 中获取消息后进行对应的处理。</p><p>这个方法里，我们既保证数据库更新成功也保证数据被发送到了 MQ 中。最终，当数据审计部门收到消息并根据消息内容做完对应的处理后，则整体数据达到最终一致的状态。</p><ol><li>只插入到 MQ 中</li></ol><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207201733667.png" alt="image-20220720173310609" style="zoom:80%;"><p>这个方式，订单模块直接收到请求后，将数据打包成消息放入到 MQ 中。</p><p>然后，再由订单模块自己和数据审计部门的服务分别从 MQ 中拿到对应的消息，再各自根据自己的业务逻辑该更新数据库的更新数据库，该走自己的审计的走自己的审计，最终达到一致的状态。</p><h2 id="最终一致性实现思路" tabindex="-1">最终一致性实现思路 <a class="header-anchor" href="#最终一致性实现思路" aria-label="Permalink to &quot;最终一致性实现思路&quot;">​</a></h2><p>在以上的例子中，我们描述了最终一致性的核心思路，不保证数据状态能实时满足业务要求，但是就像我们在线购物一样，我们能保证在间隔了一段时间窗口后肯定能满足业务需求。</p><p>然而，虽然说起来简单，但是世间上的事情又哪里那么容易呢？根据业务的不同，最终一致性分化出了多种实现思路。比如，</p><h3 id="重试-逆向模式" tabindex="-1">重试 + 逆向模式 <a class="header-anchor" href="#重试-逆向模式" aria-label="Permalink to &quot;重试 + 逆向模式&quot;">​</a></h3><p>在我们做支付时，需要记账，当记账不成功时，我们可能希望能尽可能的重试。当重试达到某种限制后，甚至我们还要通知上游系统去提供一个重试和取消接口，让下游能通知上游重发消息，或者先暂时取消操作。</p><h3 id="补救任务模式" tabindex="-1">补救任务模式 <a class="header-anchor" href="#补救任务模式" aria-label="Permalink to &quot;补救任务模式&quot;">​</a></h3><p>在我们做支付记账失败了，我们又尝试了重试 + 逆向模式取消了操作，那么此时就可以创建一个补救任务，等到后期可以保证记账成功的时候去执行这个任务。</p><h3 id="异步消息模式" tabindex="-1">异步消息模式 <a class="header-anchor" href="#异步消息模式" aria-label="Permalink to &quot;异步消息模式&quot;">​</a></h3><p>在我们做转账的时候，我们肯定是要保证 A 转出后 B 转入这种业务是强一致性的。然而，可能此时又需要跨服务。同时，我们还想尽量保证性能。那么，这个时候我们就可以先把本地对数据库的写操作和要跨服务的消息做成事务，然后，后期再根据消息被处理的状态做整体事务的提交和回滚。</p><p>可以看到，最终一致性的实现方式是多种多样的，但是，它始终逃不过一个核心，通过消息队列分发数据。在明白了这个根本原则后，以后我们理解各种各样的分布式事务，分布式共识等就会容易许多了。</p><h1 id="分布式区域问题" tabindex="-1">分布式区域问题 <a class="header-anchor" href="#分布式区域问题" aria-label="Permalink to &quot;分布式区域问题&quot;">​</a></h1><h2 id="前言-2" tabindex="-1">前言 <a class="header-anchor" href="#前言-2" aria-label="Permalink to &quot;前言&quot;">​</a></h2><p>我最近参与了公司的一个新项目，需要通过openapi接口把<code>接入方</code>的数据，比如：企业、订单、合同、物流等，同步到我们平台，然后我们平台给他们提供金融能力。</p><p>由于<code>我方</code>跟<code>对接方</code>不在同一个城市，为了提高工作效率，双方进行了多次在线视频沟通。刚开始比较顺利，没想到在沟通企业信息上传接口时，接口文档中有个非常不起眼的<code>企业注册地id</code>字段，让我们一下子进入了僵局。</p><p>到底是怎么回事呢？</p><h2 id="_1-地区问题" tabindex="-1">1.地区问题 <a class="header-anchor" href="#_1-地区问题" aria-label="Permalink to &quot;1.地区问题&quot;">​</a></h2><p>在我们平台的<code>企业表</code>中有一个<code>企业注册地id</code>字段，是必填的，用户在注册企业的页面需要选择一个地区，作为该企业的注册地，实际上数据库保存的是地区的id。</p><p>如果该企业注册成功了，会在企业详情页面上展示该地区名称。当然我们系统的后台逻辑是先通过<code>地区id</code>到<code>地区表</code>反查出地区名称，然后在用户界面中展示出来。</p><p>为了跟<code>企业表</code>保持一致，我方在定义接口文档时，企业注册地id字段也做成必填了。</p><p>当时的情况是这样的：我方地区表中有id、地区名称、国标码、等级等字段，但这里的id，是我方数据库的主键，对接方系统中肯定是没有的。对接方系统中也有一套地区表，不过id是他们的数据库id，他们的表中也有地区名称、国标码、等级等字段。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212113732.png" alt="image-20220721211311563" style="zoom:50%;"><p>所以他们系统内部需要经过一番转换，才能把我们所需的地区id传给我们。</p><h3 id="_1-1-持久化本地地区表" tabindex="-1">1.1 持久化本地地区表 <a class="header-anchor" href="#_1-1-持久化本地地区表" aria-label="Permalink to &quot;1.1 持久化本地地区表&quot;">​</a></h3><p>其实这个项目我是中途才加入的，之前在处理别的事情，我加入的时候接口文档已经定义好了。</p><p>我方跟对接方进行第二次在线沟通的时候，双方一起过接</p><p>口文档的细节，包括：接口的作用、每个参数的含义，以及他们是否有值传过来等等。</p><p>其中过到企业信息上传接口时，接口文档中有个<code>企业注册地id</code>字段，对方没法传值过来。为了解决这个问题，我方第一版的方案是：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212113444.png" alt="image-20220721211340286" style="zoom:50%;"><ol><li>对接方调用我方地区查询接口，通过多次分页查询，最终能获取我方所有地区数据，落库到他们本地的地区表。</li><li>他们在调用我方企业信息上传接口之前，先查询本地的地区表，转换成我方所需要的地区id。</li></ol><p>在讨论的过程中，对接方觉得他们也是平台，不应该做这些额外的事情。所以在那次会议中，双方针对这个问题，谁也没有说服谁，最终也没能达成共识。</p><p>后来，我思考了一下，确实这个方案太过理想化了，没有真正站到用户的角度思考问题，忽略了很多细节。可能跟文档设计者不对地区表不太熟悉有关系。</p><h3 id="_1-2-按名称调用地区查询接口" tabindex="-1">1.2 按名称调用地区查询接口 <a class="header-anchor" href="#_1-2-按名称调用地区查询接口" aria-label="Permalink to &quot;1.2 按名称调用地区查询接口&quot;">​</a></h3><p>那次会议当中，我们这边的几位同事，短暂的讨论了一下。既然对接方不愿意接受在他们本地持久化地区表，我们就退而求其次，不要求他们持久化了。这时我们这边有个同事提出，改成按名称调用地区查询接口，反查出地区id，具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212259049.png" alt="image-20220721225928907" style="zoom:50%;"><p>这个方案表面上看起来没有问题，但我之前负责过区域相关功能，我知道，就怕出现如下情况：</p><ol><li>如果对接方传的地区名称不完整，比如：本来是<code>成都市</code>，实际上传的<code>成都</code>。这样，我们地区查询接口，需要做模糊匹配，如果并发调用接口可能影响接口性能。</li><li>如果输入关键字<code>北京市</code>，在我们这边的地区表中，可以找到两条数据，一条是跟<code>省级别</code>一样的，另一条是跟<code>市级别</code>一样的。到底对应哪条数据呢？</li></ol><p>所以我当时把这两个问题抛出来了，不建议使用地区名称查询。</p><h3 id="_1-3-按国标码调用地区查询接口" tabindex="-1">1.3 按国标码调用地区查询接口 <a class="header-anchor" href="#_1-3-按国标码调用地区查询接口" aria-label="Permalink to &quot;1.3 按国标码调用地区查询接口&quot;">​</a></h3><p>那个同事听完之后，也觉得用地区名称查询有点不靠谱。他马上修改方案，改成使用地区的国标码查询地区id，具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212259265.png" alt="image-20220721225951134" style="zoom:50%;"><p>由于当时讨论时间非常短，我们没来得及考虑太多，暂且打算用这套方案。</p><h3 id="_1-4-企业上传接口入参传国标码" tabindex="-1">1.4 企业上传接口入参传国标码 <a class="header-anchor" href="#_1-4-企业上传接口入参传国标码" aria-label="Permalink to &quot;1.4 企业上传接口入参传国标码&quot;">​</a></h3><p>过了一会儿，双方继续过接口文档，重新讨论企业信息上传接口中<code>企业注册地id</code>字段传值问题。</p><p>他们在调企业信息上传接口之前，先调一下我们地区查询接口，查出地区id，入参是国标码。然后再将这个地区id，在企业信息上传接口中传过来。</p><p>对接方仔细听了我们的方案，犹豫了一下，他们觉得没有必要再调一次地区查询接口，双方都使用国标码不就行了？</p><p>他们的想法是：在企业信息上传接口中，入参由<code>企业注册地id</code>改成<code>企业注册地国标码</code>，由于国标码是国家统一的唯一编码，双方肯定是一样，能保证数据的一致性。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212300001.png" alt="image-20220721230004868" style="zoom:50%;"><h2 id="_2-想起了一个问题" tabindex="-1">2.想起了一个问题 <a class="header-anchor" href="#_2-想起了一个问题" aria-label="Permalink to &quot;2.想起了一个问题&quot;">​</a></h2><p>说实话，如果你没接触过地区功能的话，大部分人可能会同意这套方案的。</p><p>但比较巧合的是我之前正好接触过类似的功能，当时我突然想起了一个问题：<strong>双方数据的一致性如何保证？</strong></p><p>我们都知道，由于国家的发展，有些城市可能会改名，比如：<code>襄樊</code>改成了<code>襄阳</code>，另外有时候多个地级市合并成一个市，这样国标码会变化，所以国家统计网每年都会调整地区名称和国标码。</p><p>我方的地区表是两年之前创建的，数据初始化好之后没有就更新过。</p><p>而对接方不是跟我们在同一时刻初始化的数据，而且他们会定期更新地区数据，这样就导致了两边的数据不一致。如果对接方的业务表单中使用了新加的城市名和国标码，而这些信息在我方的地区表中没有，就无法查询出我方所需的地区id。</p><p>这种情况该怎么办？</p><h3 id="_2-1-双方同一时刻更新地区表" tabindex="-1">2.1 双方同一时刻更新地区表 <a class="header-anchor" href="#_2-1-双方同一时刻更新地区表" aria-label="Permalink to &quot;2.1 双方同一时刻更新地区表&quot;">​</a></h3><p>显然上面的问题是一个非常棘手的问题，这时候有些小伙伴可能会说：<code>双方使用job同一时刻更新地区表</code>，不就能解决问题了？</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212300289.png" alt="image-20220721230030186" style="zoom:80%;"><p>我不太赞成这种方案，主要原因如下：</p><ol><li>我方仅跟这个对接方有个同步执行的job，没问题。但如果还有其他的对接方，也需要调用企业信息上传接口，是不是也要整一个job，而且还要求大家都同一时刻执行，耦合性太大了。</li><li>如果我方和对接方同时执行job，但万一有任意一方执行失败了，也会导致数据不一致的情况。如果恰好这时候对接方在调用企业信息上传接口，会不会出问题？</li></ol><h3 id="_2-2-以一方的地区数据为准" tabindex="-1">2.2 以一方的地区数据为准？ <a class="header-anchor" href="#_2-2-以一方的地区数据为准" aria-label="Permalink to &quot;2.2 以一方的地区数据为准？&quot;">​</a></h3><p>上面的双方同一时刻更新地区表的方案确实有点不靠谱，但有些读者可能会问，以一方的地区数据为准，另一方把数据同步过来不就行了。具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212300632.png" alt="image-20220721230044475" style="zoom:50%;"><p>这个方案其实跟之前我方给出的第一个方案很相似，已经被对接方拒绝了。站在他们的角度来说，确实没有必要因为上传企业信息，而保存我们的地区数据。</p><p>说实话，即使他们同意了，这种跨公司跨系统的数据一致性问题，也不好保证，因为如果对接方调用我们的地区接口失败了，此时，正好在上传企业信息，是不是也有问题？</p><h2 id="_3-其他解决方案" tabindex="-1">3.其他解决方案 <a class="header-anchor" href="#_3-其他解决方案" aria-label="Permalink to &quot;3.其他解决方案&quot;">​</a></h2><p>其实，我们当时为了解决问题，还穿插着讨论过这些方案。</p><h2 id="_3-1-上传的数据存快照" tabindex="-1">3.1 上传的数据存快照 <a class="header-anchor" href="#_3-1-上传的数据存快照" aria-label="Permalink to &quot;3.1 上传的数据存快照&quot;">​</a></h2><p>我当时提出既然是保存对接方的数据，为啥不能存快照呢？我们可以把数据写到mongodb，数据格式用json，简单又高效。我的方案是：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212308218.png" alt="image-20220721230808075" style="zoom:50%;"><p>我们自己的业务数据存到mysql的业务表，而对接方的数据存在mongodb，互不干扰。</p><p>看起来，没有问题。</p><p>但是，当时产品说：银行那边规定，审查数据时只看我们mysql的业务表，其他的数据源不看。</p><p>好吧，不得不承认银行惹不起。</p><h2 id="_3-2-人工更新数据" tabindex="-1">3.2 人工更新数据 <a class="header-anchor" href="#_3-2-人工更新数据" aria-label="Permalink to &quot;3.2 人工更新数据&quot;">​</a></h2><p>另外一个同事的想法是，先让他们调用企业信息上传接口，如果发现有地区问题，我们手动帮他们调整地区表的数据。</p><p>具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212307171.png" alt="image-20220721230753015" style="zoom:50%;"><p>如果调用企业信息上传接口时，出现地区不存在的情况，则发报警邮件给指定人员。然后，指定人员手动新增或修改相关的地区数据。</p><p>这套方案看起来也可以，不过有个比较坑爹的地方就是，就怕在下班或者周末的时候出问题，反正我是不愿意去做这个事情的，你愿意吗？</p><h2 id="_3-3-提供更新接口" tabindex="-1">3.3 提供更新接口 <a class="header-anchor" href="#_3-3-提供更新接口" aria-label="Permalink to &quot;3.3 提供更新接口&quot;">​</a></h2><p>除此之外，我们还相关这套方案：对接方在调我们企业信息上传接口之前，先调我们地区查询接口查一下数据是否存在，如果不存在，则保存地区接口（保存包括：新增和修改），如果存在，则正常上传数据。</p><p>具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212307313.png" alt="image-20220721230738165" style="zoom:50%;"><p>这个方案还可以简化一下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212301006.png" alt="image-20220721230126860" style="zoom:50%;"><p>将查询并保存地区的逻辑可以放到企业信息上传接口中，这样对接方肯定非常高兴，对他们来说是透明的，地区问题不存在了。</p><p>但产品觉得地区是我们的基础数据，处于安全考虑，不能提供入口给他们修改，不然以后可能会乱套的。</p><p>这样不行，那也不行。我们一下子进入了困境，但为了不影响整体进度，只能先记录一下问题，然后跳过这个问题，继续讨论其他字段了。</p><h2 id="_4-如何解决这个问题" tabindex="-1">4.如何解决这个问题？ <a class="header-anchor" href="#_4-如何解决这个问题" aria-label="Permalink to &quot;4.如何解决这个问题？&quot;">​</a></h2><p>我当天晚上思考了良久，第二天早上，发现跟我们老大的想法不谋而合。得出的结论是，既然存在差异化，没办法避免，我们就要从系统设计上接受差异化。在企业信息上传接口中增加两个字段：<code>企业注册地国标码</code> 和 <code>地区名称</code>，对接方改成传入这两个字段，具体方案如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.6.30/202207212301190.png" alt="image-20220721230111011" style="zoom:50%;"><ol><li>在我方的企业表中增加地区名称字段，是非必填的，同时把之前的地区id字段也改成非必填。</li><li>对接方在调用我方企业信息上传接口时，同时传入地区国标码和地区名称。</li><li>我方企业信息上传接口中判断，如果通过国标码能够找到地区id，则将地区id写入db，如果找不到，则将地区名称写入db。</li></ol><p>我们评估了一下影响范围，在企业表中的地区字段，只做展示用，没有修改入口，所以上面的这套方案是可行的。</p><p>后来，再次跟对接方在线沟通时，把我们的这套方案告诉他们了，他们非常赞同。</p><h2 id="_5-总结" tabindex="-1">5 总结 <a class="header-anchor" href="#_5-总结" aria-label="Permalink to &quot;5 总结&quot;">​</a></h2><p>虽说这个地区问题，在众多技术问题中不值得一提。但是我仔细思考了一下，还是有一些宝贵的经验值得总结一下的，给有需要的小伙伴一个参考。</p><h3 id="_5-1-要从用户的角度设计接口" tabindex="-1">5.1 要从用户的角度设计接口 <a class="header-anchor" href="#_5-1-要从用户的角度设计接口" aria-label="Permalink to &quot;5.1 要从用户的角度设计接口&quot;">​</a></h3><p>在设计接口文档时，要真正做到从用户的角度出发。</p><p>尤其是像这种openapi接口，定义的参数应该尽量选择通用的，大家都认可的参数，避免出现我方定制化的参数，比如：地区id。</p><p>尽量减少用户的复杂度，让他们调用接口时更简单一些。</p><h3 id="_5-2-技术方案要有包容性" tabindex="-1">5.2 技术方案要有包容性 <a class="header-anchor" href="#_5-2-技术方案要有包容性" aria-label="Permalink to &quot;5.2 技术方案要有包容性&quot;">​</a></h3><p>技术方案要有包容性，不是非黑即白，需要有柔性的思想。在分布式环境中，如果去一味地追求数据的强一致性，不会有太好的结果。就像高并发下的商品秒杀系统，如果非要用同步方案去实现，系统最终可能会挂掉，更好的方案其实是改成异步队列处理。</p><p>我方和对接方都有地区表，数据很难保证完全一致，我们不要为了一致性而一致性，这样会适得其反。为了工作能够顺利进行下去，必然有一方要妥协，我的建议是openapi接口方做妥协，这种技术方案才够通用。</p><h3 id="_5-3-没有最好的方案-只有最适合的" tabindex="-1">5.3 没有最好的方案，只有最适合的 <a class="header-anchor" href="#_5-3-没有最好的方案-只有最适合的" aria-label="Permalink to &quot;5.3 没有最好的方案，只有最适合的&quot;">​</a></h3><p>我方最后的那个方案，其实并没有完全解决地区id找不到的问题，但是从业务的角度来看，即使没有地区id，有地区名称也是一样的。很显然，最后的方案是非常适合我们实际业务场景的。</p><p>所以没有最好的方案，只有最适合业务场景的。</p><h3 id="_5-4-进行有效的沟通" tabindex="-1">5.4 进行有效的沟通 <a class="header-anchor" href="#_5-4-进行有效的沟通" aria-label="Permalink to &quot;5.4 进行有效的沟通&quot;">​</a></h3><p>在跟对接方在线沟通时，不要因为某个问题卡壳了，而一直僵持下去。如果当时没有好的技术方案，可以先选择暂时跳过这个问题，而沟通其他的内容。后面我们再私下单独花时间，仔细思考当时的问题，从而能够提出更合理的方案。</p><h3 id="_5-5-技术是为业务服务的" tabindex="-1">5.5 技术是为业务服务的 <a class="header-anchor" href="#_5-5-技术是为业务服务的" aria-label="Permalink to &quot;5.5 技术是为业务服务的&quot;">​</a></h3><p>本文的这个地区问题，咋一看比较简单。如果一细想，会发现里面有点东西。再加上各种外部因素的限制，你会发现分布式的环境中保证地区数据一致性，并不是那么好实现。</p><p>整个过程当中，我们提出了很多种技术方案，有些方案看似可以完美解决问题，但都被我们实际的业务场景给否定了。</p><p>技术是为业务服务的，技术虽说非常重要，但是如果离开了业务都是纸上谈兵。</p><h1 id="图解-搞定分布式-程序员进阶之路" tabindex="-1">图解 | 搞定分布式，程序员进阶之路 <a class="header-anchor" href="#图解-搞定分布式-程序员进阶之路" aria-label="Permalink to &quot;图解 | 搞定分布式，程序员进阶之路&quot;">​</a></h1><blockquote><p>编程是一门艺术，它的魅力在于创造。</p></blockquote><p>65 哥已经工作两年了，一直做着简单重复的编程工作，活活熬成了一个只会 CRUD 的打工 boy。</p><blockquote><p>65 哥：总是听大佬讲分布式分布式，什么才是分布式系统呢？</p></blockquote><p>分布式系统是一个硬件或软件系统分布在不同的网络计算机上，彼此之间仅仅通过消息传递进行通信和协调的系统。在一个分布式系统中，一组独立的计算机展现给用户的是一个统一的整体，就好像是一个系统似的。系统拥有多种通用的物理和逻辑资源，可以动态的分配任务，分散的物理和逻辑资源通过计算机网络实现信息交换。</p><blockquote><p>65 哥：巴拉巴拉，能不能讲点人话。俺听不懂。</p></blockquote><p>那好，下面我们从平常最熟悉的事物开始理解分布式系统如何出现，发展的，并经过实践总结通用的理论，这些理论成为指导我们如何设计更完善的分布式系统的基础。</p><p>从此篇文章，你将学习到以下知识：</p><h2 id="web-应用的扩展" tabindex="-1">Web 应用的扩展 <a class="header-anchor" href="#web-应用的扩展" aria-label="Permalink to &quot;Web 应用的扩展&quot;">​</a></h2><p>为什么会出现分布式应用？</p><blockquote><p>65 哥：这个我也不清楚啊，以前我写的 web 应用都是直接扔进 Tomcat 中，启动 Tomcat 就可以访问了，这肯定不是分布式应用。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141703232.png" alt="image-20220814170356166" style="zoom:67%;"><p>嗯，我们就从大家最熟悉 web 后台应用讲起，以前我们的系统访问量小，业务也不复杂，一台服务器一个应用就可以处理所有的业务请求了，后来我们公司发达了，访问量上去了，业务也拓展了，虽然老板依旧没有给我们加工资，却总是埋怨我们系统不稳定，扛不住大并发，是可忍孰不可也，加钱，我们要升级。</p><blockquote><p>如果我们的服务器可以无限添加配置，那么一切性能问题都不是问题。</p></blockquote><p>为提高系统处理能力，我们首先想到的扩展方式就是升级系统配置，8 核 cpu 升级为 32 核，64 核，内存 64G 升级为 128G，256G，带宽上万兆，十万兆，这就叫做<code>垂直扩展</code>。但这样的扩展终将无法持续下去，原因如下。</p><ol><li>单机系统的处理能力最终会达到瓶颈</li><li>单机升级的边际成本将越来越大</li></ol><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141704075.png" alt="image-20220814170421991" style="zoom:50%;"><p>没有什么可以拦住我们编程打工人的步伐。</p><blockquote><p>俗话说，系统撑不住了，就加服务器，一台不行就加两台。</p></blockquote><p>当<code>垂直扩展</code>到达技术瓶颈或投入产出比超过预期，我们可以考虑通过增加服务器数量来提高并发能力，这种方式就是<code>水平扩展</code>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141703125.png" alt="image-20220814170328069" style="zoom:67%;"><h2 id="系统拆分" tabindex="-1">系统拆分 <a class="header-anchor" href="#系统拆分" aria-label="Permalink to &quot;系统拆分&quot;">​</a></h2><blockquote><p>65 哥：哦，这就是分布式系统了？这么简单的么。</p></blockquote><p>我勒个呵呵，哪有那么简单，在<code>水平扩展</code>中，我们增加了服务器数量，但是如何让这些服务器像一个整体一样对外提供稳定有效的服务才是关键。既然已经有了多台服务器，我们就要考虑如何将系统部署到到不同的节点上去。</p><blockquote><p>65 哥：这还不简单，我将我的 SpringBoot 项目部署到多台服务器上，前面加个 nginx 就可以了，现在我们的系统都是这样的，稳定高效 perfect。给你画个架构图（小声，这个我在学校时就会了。）</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141703285.png" alt="image-20220814170304236" style="zoom:67%;"><p><code>哪有什么岁月静好，只不过是有人在为你负重前行</code>。上面你所认为的简单，其实有两个原因：</p><ol><li>系统分离不彻底，很重要的一点，就是依然在共享一个数据库</li><li>在这个成熟的体系中，有太多成熟的中间件在为我们服务，比如上面提到的 nginx</li></ol><p>系统拆分也有两种方式，<code>垂直拆分</code>和<code>水平拆分</code>，注意，这里和上面提到的<code>垂直扩展</code>和<code>水平扩展</code>不是处理同一个问题的。(65 哥：哈哈，我知道，世间万物不外乎纵横二字）。</p><p>系统的<code>水平拆分</code>，就是将相同的系统部署多套，所有的节点并没有任何不同，角色和功能都一样，它们各自分担一部分功能请求，这样整个系统的处理能力的上升了。</p><p>从处理 web 请求上来看，<code>水平拆分</code>的每个节点都处理一个完整的请求，每个节点都承担一部分请求量；</p><p>从数据存储的角度看，每个数据节点都存储相同的业务数据，每个节点存储一部分数据。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141702586.png" alt="image-20220814170244524" style="zoom:67%;"><p>系统的<code>垂直拆分</code>，就是将系统按不同模块或角色拆分，不同的模块处理不同的事情。</p><p>从 web 请求上来看，需要多个相互依赖的系统配合完成一个请求，每个节点处理的需求不一致；</p><p>从数据存储角度上来看，每个数据节点都存储着各自业务模块相关的数据，它们的数据都不一样。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141702616.png" alt="image-20220814170221565" style="zoom:67%;"><p>上面<code>水平拆分</code>之后各个节点组成的就是一个<code>集群</code>，而<code>垂直拆分</code>各个节点就是<code>分布式</code>。这就是<code>集群</code>和<code>分布式</code>的区别。<code>集群</code>除了上面提到的可以提高并发处理能力外，还可以保证系统的高可用，当一部分节点失效后，整个系统依旧可以提供完整的服务。<code>分布式</code>也一样，除了提高并发能力，解耦系统，使系统边界更清晰，系统功能更内聚也是其一大好处，所以在实际的系统中我们往往这两种方式同时都在使用，而且我们常常提及的<code>分布式系统</code>其实是包含着<code>集群</code>的概念在里面的。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141702864.png" alt="image-20220814170208812" style="zoom:67%;"><h2 id="分布式目标" tabindex="-1">分布式目标 <a class="header-anchor" href="#分布式目标" aria-label="Permalink to &quot;分布式目标&quot;">​</a></h2><p>归纳和演绎是人类理性的基石，学习和思考就是不断的归纳过去的经验，从而得到普遍的规律，然后将得之的规律演绎于其他事物，用于指导更好的实践过程。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141701988.png" alt="image-20220814170151933" style="zoom:67%;"><p>上面我们讲解了分布式系统的由来，现在我们回顾和总结一下这个过程。我们引入分布式系统必然是基于现实的需求和目标而来的。</p><blockquote><p>65 哥：那分布式的目标什么呢？</p></blockquote><p>分布式就是为了满足以下目标而设计的：</p><ul><li><p>Transparency: 透明性，即用户是不关心系统背后的分布式的，无论系统是分布式的还是单机的，对用户来说都应该是透明的，用户只需要关心系统可用的能力。这里的透明性就包括以下方面：</p></li><li><ul><li>访问透明性：固定统一的访问接口和方式，不因为分布式系统内部的变动而改变系统的访问方式。</li><li>位置透明性：外部访问者不需要知道分布式系统具体的地址，系统节点的变动也不会影响其功能。</li><li>并发透明性：几个进程能并发的使用共享资源而不互相干扰。</li><li>复制透明性：使用资源的多个实例提升可靠性和性能，而用户和程序员无需知道副本的相关信息。</li><li>故障透明性：分布式系统内部部分节点的故障不影响系统的整体功能。</li><li>移动透明性：资源和客户能够在系统内移动而不受影响。</li><li>性能透明性：负载变化时，系统能够被重新配置以提高性能。</li><li>伸缩透明性：系统和应用能够进行扩展而不改变系统结构和应用算法。</li></ul></li><li><p>Openness: 开放性，通用的协议和使用方式。</p></li><li><p>Scalability: 可伸缩性，随着资源数量的增加和用户访问的增加，系统仍然能保持其有效性，该系统就被称为可伸缩的。分布式系统应该在系统大小，系统管理方面都可扩展。</p></li><li><p>Performance: 性能，相对于单体应用，分布式系统应该用更加突出的性能。</p></li><li><p>Reliability: 可靠性，与单体系统相比，分布式系统应具有更好安全性，一致性和掩盖错误的能力。</p></li></ul><h2 id="分布式挑战" tabindex="-1">分布式挑战 <a class="header-anchor" href="#分布式挑战" aria-label="Permalink to &quot;分布式挑战&quot;">​</a></h2><p>分布式的挑战来源于不确定性。想一想，分布式系统相对于单体应用，多了哪些东西？</p><blockquote><p>65 哥：有了更多的服务节点，还有就是服务之间的网络通信。</p></blockquote><p>是的，看来 65 哥同学已经懂得思考和分析系统了。分布式系统的所有挑战就来源于这两者的不确定性。</p><ol><li>节点故障：</li></ol><p>节点数量越多，出故障的概率就变高了。分布式系统需要保证故障发生的时候，系统仍然是可用的，这就需要系统能够感知所有节点的服务状态，在节点发生故障的情况下将该节点负责的计算、存储任务转移到其他节点。</p><ol><li>不可靠的网络：</li></ol><p>节点间通过网络通信，我们都知道网络是不可靠的。可能的网络问题包括：网络分割、延时、丢包、乱序。相比单机过程调用，网络通信最让人头疼的是超时已经双向通行的不确定性。出现超时状态时，网络通信发起方是无法确定当前请求是否被成功处理的。</p><p>在不可靠的网络和节点中，分布式系统依然要保证其可用，稳定，高效，这是一个系统最基本的要求。因此分布式系统的设计和架构充满了挑战。</p><h2 id="分而治之" tabindex="-1">分而治之 <a class="header-anchor" href="#分而治之" aria-label="Permalink to &quot;分而治之&quot;">​</a></h2><p>分布式系统就是充分利用更多的资源进行并行运算和存储来提升系统的性能，这就是<code>分而治之</code>的原理。</p><blockquote><p>65 哥：哦，懂了懂了，那 MapReduce 的 map，Elasticsearch 的 sharding，Kafka 的 partition 是不是都是分布式的分而治之原理。</p></blockquote><p>可以啊，65 哥同学不仅能够归纳，还能够举一反三了。不错，无论是 map，sharding 还是 partition，甚至 <code>请求路由负载均衡</code> 都是在将计算或数据拆分，再分布到不同的节点计算和存储，从而提高系统的并发性。</p><h3 id="不同集群类型的分" tabindex="-1">不同集群类型的分 <a class="header-anchor" href="#不同集群类型的分" aria-label="Permalink to &quot;不同集群类型的分&quot;">​</a></h3><h4 id="sharding" tabindex="-1">sharding <a class="header-anchor" href="#sharding" aria-label="Permalink to &quot;sharding&quot;">​</a></h4><p>同样是<code>分</code>，在不同领域的，甚至不同实现的系统中通常会有不同的说法。sharding 通常是在数据存储系统中将不同数据分布到不同节点的方式，中文通常翻译为<code>数据分片</code>。</p><p>比如在 MongoDB 中，当 MongoDB 存储海量的数据时，一台机器可能不足以存储数据，也可能不足以提供可接受的读写吞吐量。这时，我们就可以通过在多台机器上分割数据，使得数据库系统能存储和处理更多的数据。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141700668.png" alt="image-20220814170058605" style="zoom:67%;"><p>比如在 Elasticsearch 中，每个索引有一个或多个分片，索引的数据被分配到各个分片上，相当于一桶水用了 N 个杯子装。分片有助于横向扩展，N 个分片会被尽可能平均地（rebalance）分配在不同的节点上。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141701668.png" alt="image-20220814170112605" style="zoom:67%;"><h4 id="partition" tabindex="-1">partition <a class="header-anchor" href="#partition" aria-label="Permalink to &quot;partition&quot;">​</a></h4><p><code>partition</code>的概念经常在 Kafka 中可以看到，在 kafka 中 topic 是一个逻辑概念，从分布式队列的角度看，topic 对使用者来说就是一个队列，topic 在 kafka 的具体实现中，由分布在不同节点上的 partition 组成，每个 partition 就是根据分区算法拆分的多个分区，在 kafka 中，同一个分区不能被同一个 group 下的多个 consumer 消费，所以一个 topic 有多少 partition 在一定意义上就表示这个 topic 具有多少并发处理能力。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141701640.png" alt="image-20220814170126565" style="zoom:67%;"><p>在 Amazing 的分布式数据库<code>DynamoDB</code>中，一张表在底层实现中也被分区为不同的 partition。</p><h4 id="load-balance" tabindex="-1">load balance <a class="header-anchor" href="#load-balance" aria-label="Permalink to &quot;load balance&quot;">​</a></h4><p>负载均衡是高可用网络基础架构的关键组件，通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。</p><p>比如 nginx 的负载均衡，通过不同的负载均衡分配策略，将 http 请求分发到 web 应用的不同节点之上，从而提高应用的并发处理能力。</p><p>比如 dubbo 的客户端负载能力，可以将 dubbo 请求路由到具体的 producer 提供节点上，负载均衡是一个完善的 RPC 所应该具有的能力。</p><p>在 Spring Cloud 的体系中 Robbin 组件可以通过 Spring Cloud 的各微服务之间通信的负载均衡分配问题，依旧是将请求分发到集群中的不同节点上去。</p><h3 id="分的策略" tabindex="-1">分的策略 <a class="header-anchor" href="#分的策略" aria-label="Permalink to &quot;分的策略&quot;">​</a></h3><p>无论是分区还是分片，还是分区路由，其实都有一些通用的分区算法，以下的概念可能很多同学都在不同的领域看到过，如上面看到的反向代理服务器 nginx 中，如分布式消息队列 kafka 中，如 RPC 框架 Dubbo 中，这有时候会让很多同学感到懵。</p><p>其实无论在什么领域中，你只要抓住它在完成的核心功能上就可以理解，它们就是在考虑如何<code>分</code>的问题，把处理请求（即计算）如何<code>均匀</code>地分到不同的机器上，把数据如何分配到不同的节点上。</p><p>从大的方向看<code>分</code>有两种策略，一种<code>可复刻</code>，一种<code>不可复刻</code>。</p><p><code>可复刻</code>，这种策略根据一定算法分配计算和数据，在相同的条件下，无论什么时间点得出的结果相同，因此对于相同条件的请求和数据来说是<code>可复刻</code>的，在不同时间点相同的请求和数据始终都在统一节点上。这种策略一般用于有数据状态在情况。</p><p><code>不可复刻</code>，这种策略使用全随机方式，即使在相同的条件下，不同时间点得出的结果也不一致，因此也是<code>不可还原</code>的，如果只是为了<code>可还原</code>，如果通过元数据记录已经分配好的数据，之后需要<code>还原</code>时通过元数据就可以准确的得知数据所在位置了。</p><blockquote><p>65 哥：这么神奇么？我想看看不同系统都有什么策略。</p></blockquote><h4 id="dubbo-的负载均衡" tabindex="-1">Dubbo 的负载均衡 <a class="header-anchor" href="#dubbo-的负载均衡" aria-label="Permalink to &quot;Dubbo 的负载均衡&quot;">​</a></h4><p>Dubbo 是阿里开源的分布式服务框架。其实现了多种负载均衡策略。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/FbXJ7UCc6O0fHEDNGx2yB9n0FqHupQ36sPGhAuUZQ2pu6qLUwwBbfMvTJzo70h8QGIGG8O4ECiaNGCUTXUtRUlQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h5 id="random-loadbalance" tabindex="-1">Random LoadBalance <a class="header-anchor" href="#random-loadbalance" aria-label="Permalink to &quot;Random LoadBalance&quot;">​</a></h5><p>随机，可以按权重设置随机概率。在一个截面上碰撞的概率高，但调用量越大分布越均匀，而且按概率使用权重后也比较均匀，有利于动态调整提供者权重。</p><h5 id="roundrobin-loadbalance" tabindex="-1">RoundRobin LoadBalance <a class="header-anchor" href="#roundrobin-loadbalance" aria-label="Permalink to &quot;RoundRobin LoadBalance&quot;">​</a></h5><p>轮询，按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题，比如：第二台机器很慢，但没挂，当请求调到第二台时就卡在那，久而久之，所有请求都卡在调到第二台上。</p><h5 id="leastactive-loadbalance" tabindex="-1">LeastActive LoadBalance <a class="header-anchor" href="#leastactive-loadbalance" aria-label="Permalink to &quot;LeastActive LoadBalance&quot;">​</a></h5><p>最少活跃调用数，相同活跃数的随机，活跃数指调用前后计数差。使慢的提供者收到更少请求，因为越慢的提供者的调用前后计数差会越大。</p><h5 id="consistenthash-loadbalance" tabindex="-1">ConsistentHash LoadBalance <a class="header-anchor" href="#consistenthash-loadbalance" aria-label="Permalink to &quot;ConsistentHash LoadBalance&quot;">​</a></h5><p>一致性 Hash，相同参数的请求总是发到同一提供者。当某一台提供者挂时，原本发往该提供者的请求，基于虚拟节点，平摊到其它提供者，不会引起剧烈变动。</p><h4 id="kafka-的分区分配策略" tabindex="-1">Kafka 的分区分配策略 <a class="header-anchor" href="#kafka-的分区分配策略" aria-label="Permalink to &quot;Kafka 的分区分配策略&quot;">​</a></h4><p>Kafka 中提供了多重分区分配算法（PartitionAssignor）的实现：</p><h5 id="rangeassignor" tabindex="-1">RangeAssignor <a class="header-anchor" href="#rangeassignor" aria-label="Permalink to &quot;RangeAssignor&quot;">​</a></h5><p><img src="https://mmbiz.qpic.cn/mmbiz_png/FbXJ7UCc6O0fHEDNGx2yB9n0FqHupQ36Cf596KcQFQS4qpck5T5fXBuFicEVdI7WKNZpP10WmVnNpL3fnqeh9Uw/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><p>RangeAssignor 策略的原理是按照消费者总数和分区总数进行整除运算来获得一个跨度，然后将分区按照跨度进行平均分配，以保证分区尽可能均匀地分配给所有的消费者。对于每一个 Topic，RangeAssignor 策略会将消费组内所有订阅这个 Topic 的消费者按照名称的字典序排序，然后为每个消费者划分固定的分区范围，如果不够平均分配，那么字典序靠前的消费者会被多分配一个分区。</p><h5 id="roundrobinassignor" tabindex="-1">RoundRobinAssignor <a class="header-anchor" href="#roundrobinassignor" aria-label="Permalink to &quot;RoundRobinAssignor&quot;">​</a></h5><p>RoundRobinAssignor 的分配策略是将消费组内订阅的所有 Topic 的分区及所有消费者进行排序后尽量均衡的分配（RangeAssignor 是针对单个 Topic 的分区进行排序分配的）。</p><h5 id="stickyassignor" tabindex="-1">StickyAssignor <a class="header-anchor" href="#stickyassignor" aria-label="Permalink to &quot;StickyAssignor&quot;">​</a></h5><p>从字面意义上看，Sticky 是“粘性的”，可以理解为分配结果是带“粘性的”——每一次分配变更相对上一次分配做最少的变动（上一次的结果是有粘性的），其主要是为了实现以下两个目标：</p><ol><li>分区的分配尽量的均衡</li><li>每一次重分配的结果尽量与上一次分配结果保持一致</li></ol><blockquote><p>65 哥：哇，看来优秀的系统都是相通的。</p></blockquote><h2 id="副本" tabindex="-1">副本 <a class="header-anchor" href="#副本" aria-label="Permalink to &quot;副本&quot;">​</a></h2><p>副本是解决分布式集群高可用问题的。在集群系统中，每个服务器节点都是不可靠的，每个系统都有宕机的风险，如何在系统中少量节点失效的情况下保证整个系统的可用性是分布式系统的挑战之一。副本就是解决这类问题的方案。副本同样也可以提高并发处理能力，比如数据在不同的节点上可以读写分离，可以并行读等。</p><p>在这里其实也有很多说法，如 Master-Salve、Leader-Follower、Primary-Shard、Leader-Replica 等等。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141657779.png" alt="image-20220814165723693" style="zoom:67%;"><h3 id="mysql的主从架构" tabindex="-1">MySQL的主从架构 <a class="header-anchor" href="#mysql的主从架构" aria-label="Permalink to &quot;MySQL的主从架构&quot;">​</a></h3><p>目前，大部分的主流关系型数据库都提供了主从热备功能，通过配置两台（或多台）数据库的主从关系，可以将一台数据库服务器的数据更新同步到另一台服务器上。这既可以实现数据库的读写分离，从而改善数据库的负载压力，也可以提高数据高可用，多份数据备份降低了数据丢失的风险。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141657300.png" alt="image-20220814165749237" style="zoom:67%;"><h3 id="elasticsearch-的副本机制" tabindex="-1">Elasticsearch 的副本机制 <a class="header-anchor" href="#elasticsearch-的副本机制" aria-label="Permalink to &quot;Elasticsearch 的副本机制&quot;">​</a></h3><p>在 ES 中有主分片和副本分片的概念。副本分片的主要目的就是为了故障转移，如果持有主分片的节点挂掉了，一个副本分片就会晋升为主分片的角色从而对外提供查询服务。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141658810.png" alt="image-20220814165806742" style="zoom:67%;"><h2 id="cap-理论" tabindex="-1">CAP 理论 <a class="header-anchor" href="#cap-理论" aria-label="Permalink to &quot;CAP 理论&quot;">​</a></h2><p>在理论计算机科学中，CAP 定理（CAP theorem），又被称作布鲁尔定理（Brewer&#39;s theorem），它指出对于一个分布式计算系统来说，不可能同时满足分布式系统一致性、可用性和分区容错（即 CAP 中的&quot;C&quot;，&quot;A&quot;和&quot;P&quot;）:</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141656474.png" alt="image-20220814165602409" style="zoom:67%;"><blockquote><p>65 哥：什么是一致性、可用性和分区容错性能？</p></blockquote><ul><li>一致性 (Consistency)</li></ul><p>一致性意味着所有客户端同时看到相同的数据，无论它们连接到哪个节点。要发生这种情况，每当将数据写入一个节点时，必须立即将数据转发或复制到系统中的所有其他节点，然后才能将写入视为&quot;成功&quot;。</p><ul><li>可用性 (Availability)</li></ul><p>任何客户端的请求都能得到响应数据，不会出现响应错误。换句话说，可用性是站在分布式系统的角度，对访问本系统的客户的另一种承诺：我一定会给您返回数据，不会给你返回错误，但不保证数据最新，强调的是不出错。</p><ul><li>分区容错</li></ul><p>分区即分布式系统中的通信中断，两个节点之间的丢失或暂时延迟的连接。分区容错意味着群集必须继续工作，尽管系统中的节点之间存在的通信故障。</p><p>这三种性质进行俩俩组合，可以得到下面三种情况：</p><ul><li>CA：完全严格的仲裁协议，例如 2PC（两阶段提交协议，第一阶段投票，第二阶段事物提交）</li><li>CP：不完全（多数）仲裁协议，例如 Paxos、Raft</li><li>AP：使用冲突解决的协议，例如 Dynamo、Gossip</li></ul><p>CA 和 CP 系统设计遵循的都是强一致性理论。不同的是 CA 系统不能容忍节点发生故障。CP 系统能够容忍 2f+1 个节点中有 f 个节点发生失败。</p><h2 id="base-理论-1" tabindex="-1">Base 理论 <a class="header-anchor" href="#base-理论-1" aria-label="Permalink to &quot;Base 理论&quot;">​</a></h2><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208141654901.png" alt="image-20220814165419830" style="zoom:67%;"><p>CAP 理论表明，对于一个分布式系统而言，它是无法同时满足 Consistency（强一致性）、Availability（可用性） 和 Partition tolerance（分区容忍性） 这三个条件的，最多只能满足其中两个。</p><p>在分布式环境中，我们会发现必须选择 P（分区容忍）要素，因为网络本身无法做到 100% 可靠，有可能出故障，所以分区是一个必然的现象。也就是说分区容错性是分布式系统的一个最基本要求。</p><p>CAP 定理限制了我们三者无法同时满足，但我们可以尽量让 C、A、P 都满足，这就是 BASE 定理。</p><p>BASE 理论是 Basically Available（基本可用），Soft State（软状态）和 Eventually Consistent（最终一致性）三个短语的缩写。即使无法做到强一致性（Strong consistency），但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性（Eventual consistency）。</p><h3 id="基本可用-basically-available" tabindex="-1">基本可用 (Basically Available) <a class="header-anchor" href="#基本可用-basically-available" aria-label="Permalink to &quot;基本可用 (Basically Available)&quot;">​</a></h3><p>基本可用是指分布式系统在出现故障的时候，允许损失部分可用性，即保证核心可用。</p><p>电商大促时，为了应对访问量激增，部分用户可能会被引导到降级页面，服务层也可能只提供降级服务，这就是损失部分可用性的体现。</p><h3 id="软状态-soft-state" tabindex="-1">软状态 ( Soft State) <a class="header-anchor" href="#软状态-soft-state" aria-label="Permalink to &quot;软状态 ( Soft State)&quot;">​</a></h3><p>什么是软状态呢？相对于原子性而言，要求多个节点的数据副本都是一致的，这是一种“硬状态”。</p><p>软状态指的是：允许系统中的数据存在中间状态，并认为该状态不影响系统的整体可用性，即允许系统在多个不同节点的数据副本存在数据延时。</p><h3 id="最终一致性-eventual-consistency" tabindex="-1">最终一致性 ( Eventual Consistency) <a class="header-anchor" href="#最终一致性-eventual-consistency" aria-label="Permalink to &quot;最终一致性 ( Eventual Consistency)&quot;">​</a></h3><p>最终一致性是指系统中的所有数据副本经过一定时间后，最终能够达到一致的状态。</p><p>弱一致性和强一致性相反，最终一致性是弱一致性的一种特殊情况。</p><p>BASE 理论面向的是大型高可用、可扩展的分布式系统。与传统 ACID 特性相反，不同于 ACID 的强一致性模型，BASE 提出通过牺牲强一致性来获得可用性，并允许数据段时间内的不一致，但是最终达到一致状态。</p><p>分布式是系统扩展的必然方向，分布式系统所遇到的问题是普遍，随着大量优秀的项目在分布式的道路上披荆斩棘，前人已经总结了大量丰富的理论。并且不同领域的分布式系统也层出不穷，我们既应该学习好这些好的理论知识，也应该去多看看不同分布式系统的实现，总结它们的共性，发现它们在不同领域独特的亮点权衡，更重要的，我们应该将所学用于日常项目的实践当中，也应该在实践中总结出更多的规律理论。</p><p>感谢读者看完本文，<code>码哥</code>将为读者持续输出高质量的文章，下期我们继续深入讲讲分布式一致性的问题和解决方案，敬请关注。</p><h1 id="java中的分布式锁" tabindex="-1">Java中的分布式锁 <a class="header-anchor" href="#java中的分布式锁" aria-label="Permalink to &quot;Java中的分布式锁&quot;">​</a></h1><p>首先，分布式锁和我们平常讲到的锁原理基本一样，目的就是确保在多个线程并发时，只有一个线程在同一刻操作这个业务或者说方法、变量。</p><p>在一个进程中，也就是一个jvm或者说应用中，我们很容易去处理控制，在jdk java.util并发包中已经为我们提供了这些方法去加锁，比如synchronized关键字或者Lock锁，都可以处理。</p><p>但是我们现在的应用程序如果只部署一台服务器，那并发量是很差的，如果同时有上万的请求，很有可能造成服务器压力过大而瘫痪。想想双十一和大年三十晚上十点，瓜分支付宝红包等业务场景，自然需要用到多台服务器去同时处理这些业务，这些服务可能会有上百台同时处理。</p><p>但是我们想一想，如果有100台服务器要处理分红包的业务，现在假设有1亿的红包，1千万个人分，金额随机，那么这个业务场景下，是不是必须确保这1千万个人最后分的红包金额总和等于1亿？</p><p>如果处理不好每人分到100万，那马云爸爸估计大年初一，就得宣布破产了</p><h2 id="_1、常规锁会造成什么情况" tabindex="-1">1、常规锁会造成什么情况 <a class="header-anchor" href="#_1、常规锁会造成什么情况" aria-label="Permalink to &quot;1、常规锁会造成什么情况&quot;">​</a></h2><p>首先说一下我们为什么要搞集群。</p><p>简单理解就是，需求量（请求并发量）变大了，一个工人处理能力有限，那就多招一些工人来一起处理。</p><p>假设1千万个请求平均分配到100台服务器上，每个服务器接收10w的请求。这10w个请求并不是在同一秒中来的，可能是在1,2个小时内，可以联想下我们三十晚上开红包，等到10：20开始，有的人立马开了，有的人等到12点才想起来。</p><p>那这样的话，平均到每一秒上的请求也就不到1千个，这种压力一般的服务器还是可以承受的。</p><ul><li>第一个用户来分，请求到来后，需要在1亿里面给他分一部分钱，金额随机，假设第一个人分到了100，那就要在这1亿中减去100块，剩下99999900块~</li><li>第二个用户再来分，金额随机，这次分200块，那就需要在剩下的99999900块中再减去200块，剩下99999700块。</li><li>等到第10w个用户来，一看还有1000w，那这1000w全成他的了。</li></ul><p>等于是在每个服务器中去分1亿，也就是10w个用户分了一个亿，最后总计有100个服务器，要分100亿。</p><p>如果真这样了，虽说马云爸爸不会破产（据最新统计马云有2300亿人民币），那分红包的开发项目组，以及产品经理，可以GG了~</p><p>简化结构图如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208201722162.png" alt="image-20220820172249096" style="zoom:67%;"><h2 id="_2、分布式锁怎么去处理" tabindex="-1">2、分布式锁怎么去处理 <a class="header-anchor" href="#_2、分布式锁怎么去处理" aria-label="Permalink to &quot;2、分布式锁怎么去处理&quot;">​</a></h2><p>那么为了解决这个问题，让1000万用户只分1亿，而不是100亿，这个时候分布式锁就派上用处了。</p><p>分布式锁可以把整个集群就当作是一个应用一样去处理，那么也就需要这个锁独立于每一个服务之外，而不是在服务里面。</p><p>假设第一个服务器接收到用户1的请求后，不能只在自己的应用中去判断还有多少钱可以分了，而需要去外部请求专门负责管理这1亿红包的人（服务），问他：哎，我这里要分100块，给我100。</p><p>管理红包的妹子（服务）一看，还有1个亿，那好，给你100块，然后剩下99999900块。</p><p>第二个请求到来后，被服务器2获取，继续去询问，管理红包的妹子，我这边要分10块，管理红包的妹子先查了下还有99999900，那就说：好，给你10块。那就剩下99999890块。</p><p>等到第1000w个请求到来后，服务器100拿到请求，继续去询问，管理红包的妹子，我要100，妹子翻了翻白眼，对你说，就剩1块了，爱要不要，那这个时候就只能给你1块了（1块也是钱啊，买根辣条还是可以的）。</p><p>这些请求编号1,2不代表执行的先后顺序，正式的场景下，应该是100台服务器每个服务器持有一个请求去访问负责管理红包的妹子（服务），那在管红包的妹子那里同时会接收到100个请求，这个时候就需要在负责红包的妹子那里加个锁就可以了（抛绣球），你们100个服务器谁拿到锁（抢到绣球），谁就进来和我谈，我给你分，其他人就等着去吧。</p><p>经过上面的分布式锁的处理后，马云爸爸终于放心了，决定给红包团队每人加一个鸡腿。</p><p>简化的结构图如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.8.30/202208201750658.png" alt="image-20220820175039596" style="zoom:67%;"><h2 id="_3、分布式锁的实现有哪些" tabindex="-1">3、分布式锁的实现有哪些？ <a class="header-anchor" href="#_3、分布式锁的实现有哪些" aria-label="Permalink to &quot;3、分布式锁的实现有哪些？&quot;">​</a></h2><p>说到分布式锁的实现，还是有很多的，有数据库方式的，有Redis分布式锁，有Zookeeper分布式锁等等。</p><p>我们如果采用Redis作为分布式锁，那么上图中负“责红包的妹子（服务）”，就可以替换成Redis，请自行脑补。推荐：<a href="https://mp.weixin.qq.com/s?__biz=MzIyNDU2ODA4OQ==&amp;mid=2247486846&amp;idx=1&amp;sn=75bd4dcdbaad73191a1e4dbf691c118a&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">Java面试练题宝典</a></p><h3 id="_1、为什么redis可以实现分布式锁" tabindex="-1">1、为什么Redis可以实现分布式锁？ <a class="header-anchor" href="#_1、为什么redis可以实现分布式锁" aria-label="Permalink to &quot;1、为什么Redis可以实现分布式锁？&quot;">​</a></h3><p>首先Redis是单线程的，这里的单线程指的是网络请求模块使用了一个线程（所以不需考虑并发安全性），即一个线程处理所有网络请求，其他模块仍用了多个线程。</p><p>在实际的操作中过程大致是这样子的：</p><p>服务器1要去访问发红包的妹子，也就是Redis，那么他会在Redis中通过&quot;setnx key value&quot; 操作设置一个key进去，value是啥不重要，重要的是要有一个key，也就是一个标记，而且这个key你爱叫啥叫啥，只要所有的服务器设置的key相同就可以。</p><p>假设我们设置一个，如下：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">setnx jjj nice</span></span></code></pre></div><p>那么我们可以看到会返回一个1，那就代表了成功。</p><p>如果再来一个请求去设置同样的key，如下：</p><div class="language-apl"><button title="Copy Code" class="copy"></button><span class="lang">apl</span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">setnx jjj good</span></span></code></pre></div><p>这个时候会返回0，那就代表失败了。</p><p>那么我们就可以通过这个操作去判断是不是当前可以拿到锁，或者说可以去访问“负责发红包的妹子”，如果返回1，那我就开始去执行后面的逻辑，如果返回0，那就说明已经被人占用了，我就要继续等待。</p><p>当服务器1拿到锁之后，进行了业务处理，完成后，还需要释放锁，如下图所示：</p><p>删除成功返回1，那么其他的服务器就可以继续重复上面的步骤去设置这个key，以达到获取锁的目的。</p><p>当然以上的操作是在Redis客户端直接进行的，通过程序调用的话，肯定就不能这么写，比如java就需要通过jedis去调用，但是整个处理逻辑基本都是一样的。</p><p>通过上面的方式，我们好像是解决了分布式锁的问题，但是想想还有没有什么问题呢？</p><p>对，问题还是有的，可能会有死锁的问题发生，比如服务器1设置完之后，获取了锁之后，忽然发生了宕机。</p><p>那后续的删除key操作就没法执行，这个key会一直在Redis中存在，其他服务器每次去检查，都会返回0，他们都会认为有人在使用锁，我需要等。</p><p>为了解决这个死锁的问题，我们就需要给key设置有效期了。</p><p>设置的方式有2种：</p><ul><li>第一种就是在set完key之后，直接设置key的有效期 &quot;expire key timeout&quot; ，为key设置一个超时时间，单位为second，超过这个时间锁会自动释放，避免死锁。</li></ul><p>这种方式相当于，把锁持有的有效期，交给了Redis去控制。如果时间到了，你还没有给我删除key，那Redis就直接给你删了，其他服务器就可以继续去setnx获取锁。</p><ul><li>第二种方式，就是把删除key权利交给其他的服务器，那这个时候就需要用到value值了，比如服务器1，设置了value也就是timeout为当前时间+1秒 ，这个时候服务器2通过get发现时间已经超过系统当前时间了，那就说明服务器1没有释放锁，服务器1可能出问题了，服务器2就开始执行删除key操作，并且继续执行setnx操作。</li></ul><p>但是这块有一个问题，也就是不光你服务器2可能会发现服务器1超时了，服务器3也可能会发现，如果刚好服务器2 setnx操作完成，服务器3就接着删除，是不是服务器3也可以setnx成功了？</p><p>那就等于是服务器2和服务器3都拿到锁了，那就问题大了。这个时候怎么办呢？</p><p>这个时候需要用到“GETSET key value”命令了。这个命令的意思就是获取当前key的值，并且设置新的值。</p><p>假设服务器2发现key过期了，开始调用getset命令，然后用获取的时间判断是否过期，如果获取的时间仍然是过期的，那就说明拿到锁了。</p><p>如果没有，则说明在服务2执行getset之前，服务器3可能也发现锁过期了，并且在服务器2之前执行了getset操作，重新设置了过期时间。</p><p>那么服务器2就需要放弃后续的操作，继续等待服务器3释放锁或者去监测key的有效期是否过期。</p><p>这块其实有一个小问题是，服务器3已经修改了有效期，拿到锁之后，服务器2也修改了有效期，但是没能拿到锁，但是这个有效期的时间已经被在服务器3的基础上有增加一些，但是这种影响其实还是很小的，几乎可以忽略不计。</p><h3 id="_2、为什么zookeeper可实现分布式锁" tabindex="-1">2、为什么Zookeeper可实现分布式锁？ <a class="header-anchor" href="#_2、为什么zookeeper可实现分布式锁" aria-label="Permalink to &quot;2、为什么Zookeeper可实现分布式锁？&quot;">​</a></h3><p>百度百科是这么介绍的：ZooKeeper是一个分布式的，开放源码的分布式应用程序协调服务，是Google的Chubby一个开源的实现，是Hadoop和Hbase的重要组件。</p><p>那对于我们初次认识的人，可以理解成ZooKeeper就像是我们的电脑文件系统，我们可以在d盘中创建文件夹a，并且可以继续在文件夹a中创建文件夹a1，a2。</p><p>那我们的文件系统有什么特点？那就是同一个目录下文件名称不能重复，同样ZooKeeper也是这样的。</p><p>在ZooKeeper所有的节点，也就是文件夹称作Znode，而且这个Znode节点是可以存储数据的。</p><p>我们可以通过“ create /zkjjj nice”来创建一个节点，这个命令就表示，在跟目录下创建一个zkjjj的节点，值是nice。同样这里的值，和我在前面说的Redis中的一样，没什么意义，你随便给。</p><p>另外ZooKeeper可以创建4种类型的节点，分别是：</p><ul><li>持久性节点</li><li>持久性顺序节点</li><li>临时性节点</li><li>临时性顺序节点</li></ul><p>首先说下持久性节点和临时性节点的区别：</p><ul><li>持久性节点表示只要你创建了这个节点，那不管你ZooKeeper的客户端是否断开连接，ZooKeeper的服务端都会记录这个节点；</li><li>临时性节点刚好相反，一旦你ZooKeeper客户端断开了连接，那ZooKeeper服务端就不再保存这个节点；</li><li>顺便也说下顺序性节点，顺序性节点是指，在创建节点的时候，ZooKeeper会自动给节点编号比如0000001，0000002这种的。</li></ul><p>Zookeeper有一个监听机制，客户端注册监听它关心的目录节点，当目录节点发生变化（数据改变、被删除、子目录节点增加删除）等，Zookeeper会通知客户端。推荐：<a href="https://mp.weixin.qq.com/s?__biz=MzIyNDU2ODA4OQ==&amp;mid=2247486846&amp;idx=1&amp;sn=75bd4dcdbaad73191a1e4dbf691c118a&amp;scene=21#wechat_redirect" target="_blank" rel="noreferrer">Java面试练题宝典</a></p><h2 id="_4、在zookeeper中如何加锁" tabindex="-1">4、在Zookeeper中如何加锁？ <a class="header-anchor" href="#_4、在zookeeper中如何加锁" aria-label="Permalink to &quot;4、在Zookeeper中如何加锁？&quot;">​</a></h2><p>下面我们继续结合我们上面的分红包场景，描述下在Zookeeper中如何加锁。</p><p>假设服务器1，创建了一个节点 /zkjjj，成功了，那服务器1就获取了锁，服务器2再去创建相同的锁，就会失败，这个时候就只能监听这个节点的变化。</p><p>等到服务器1处理完业务，删除了节点后，他就会得到通知，然后去创建同样的节点，获取锁处理业务，再删除节点，后续的100台服务器与之类似。</p><p>注意这里的100台服务器并不是挨个去执行上面的创建节点的操作，而是并发的，当服务器1创建成功，那么剩下的99个就都会注册监听这个节点，等通知，以此类推。</p><p>但是大家有没有注意到，这里还是有问题的，还是会有死锁的情况存在，对不对？</p><p>当服务器1创建了节点后挂了，没能删除，那其他99台服务器就会一直等通知，那就完蛋了。。。</p><p>这个时候就需要用到<code>临时性节点</code>了，我们前面说过了，临时性节点的特点是客户端一旦断开，就会丢失，也就是当服务器1创建了节点后，如果挂了，那这个节点会自动被删除，这样后续的其他服务器，就可以继续去创建节点，获取锁了。</p><p>但是我们可能还需要注意到一点，就是<code>惊群效应</code>：举一个很简单的例子，当你往一群鸽子中间扔一块食物，虽然最终只有一个鸽子抢到食物，但所有鸽子都会被惊动来争夺，没有抢到…</p><p>就是当服务器1节点有变化，会通知其余的99个服务器，但是最终只有1个服务器会创建成功，这样98还是需要等待监听，那么为了处理这种情况，就需要用到临时顺序性节点。大致意思就是，之前是所有99个服务器都监听一个节点，<code>现在就是每一个服务器监听自己前面的一个节点</code>。</p><p>假设100个服务器同时发来请求，这个时候会在/zkjjj节点下创建100个临时顺序性节点<code>/zkjjj/000000001</code>，<code>/zkjjj/000000002</code>，一直到<code>/zkjjj/000000100</code>，这个编号就等于是已经给他们设置了获取锁的先后顺序了。</p><p>当001节点处理完毕，删除节点后，002收到通知，去获取锁，开始执行，执行完毕，删除节点，通知003~以此类推。</p><h1 id="浅谈分布式一致性协议之2pc" tabindex="-1">浅谈分布式一致性协议之2PC <a class="header-anchor" href="#浅谈分布式一致性协议之2pc" aria-label="Permalink to &quot;浅谈分布式一致性协议之2PC&quot;">​</a></h1><h2 id="_1-单机事务和分布式事务" tabindex="-1">1. 单机事务和分布式事务 <a class="header-anchor" href="#_1-单机事务和分布式事务" aria-label="Permalink to &quot;1. 单机事务和分布式事务&quot;">​</a></h2><p>在聊2PC和3PC之前，我们有必要先了解下<strong>单机事务</strong>和<strong>分布式事务</strong>。</p><p>事务是一组原子操作，<strong>要么全成功要么全失败 All or Nothing</strong>，否则就会出现数据混乱，这种需求在<strong>金融和电商</strong>领域很普遍。试想你去天猫买了台mac，订单服务、扣款服务、库存服务出现任何一个环节的失败都会带来问题，所以就需要事务来做保证，<strong>一荣俱荣一损俱损</strong>。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101611744.png" alt="image-20221210161112563" style="zoom:67%;"><p>单机事务具备ACID特性，大家都比较喜欢，但是单机的能力毕竟有限，我们常常必须在分布式场景下同样完成这一组原子操作，也就是分布式事务。</p><p>由于分布式系统中各个子服务是部署在<strong>不同的物理节点</strong>上，<strong>不同交换机</strong>、<strong>不同机房、不同城市</strong>，这样以来，要想<strong>像单机系统一样完成这一组原子操作就没那么容易了</strong>，因此就出现了很多分布式一致性协议和算法，来解决分布式事务问题保证数据一致性。</p><p>分布式系统的各个节点只能依靠<strong>网络</strong>来进行数据通信，但是网络往往<strong>并不完全可靠</strong>，同时节点<strong>物理故障</strong>也时常发生，在这样一个<strong>复杂的环境</strong>中去保证数据一致性确实不太容易。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101611273.png" alt="image-20221210161125138" style="zoom:67%;"><h2 id="_2-一致性协议的一些思路" tabindex="-1">2 一致性协议的一些思路 <a class="header-anchor" href="#_2-一致性协议的一些思路" aria-label="Permalink to &quot;2 一致性协议的一些思路&quot;">​</a></h2><p>突兀地去学习一个新的协议或者算法往往比较生硬，不如思考一下生活现象，大部分的计算机领域的算法和思想在实际生活中都有映像，又或者说<strong>算法来源于生活</strong>。</p><blockquote><p><strong>举个栗子</strong>：在规模盛大的阅兵仪式中会有陆海空多兵种多方队，为了让命令和节奏准确地下达，我们<strong>需要乐曲、指挥、领队、排头、队员等多种角色</strong>。整个活动需要在不同角色的相互作用之下，才能让一个数量庞大的个体组成一个整体来完成一个目标。</p></blockquote><blockquote><p><strong>再举个栗子</strong>：2018年5月在西安举行了一场盛大的<strong>无人机编组飞行灯光秀</strong>，共计1374架无人机组成一个庞大的编组来完成灯光表演，但是还是出现了问题，并未上演完美图案：</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101612029.png" alt="image-20221210161207318" style="zoom:50%;"><p>分布式系统也是如此，为了让很多参与的<strong>机器节点节奏步调一致</strong>，我们就需要不同的角色各司其职，共同完成一项任务，但是仍然困难重重，因为会有网络故障和节点物理故障等问题。</p><h2 id="_3-2pc二阶段提交协议基本过程" tabindex="-1">3 2PC二阶段提交协议基本过程 <a class="header-anchor" href="#_3-2pc二阶段提交协议基本过程" aria-label="Permalink to &quot;3 2PC二阶段提交协议基本过程&quot;">​</a></h2><blockquote><p>要理解2PC协议重点在于<strong>节点角色分工</strong>和<strong>两个阶段所执行的动作</strong>以及<strong>不同情况下的处理逻辑</strong>。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101613751.png" alt="image-20221210161304583" style="zoom:80%;"><h3 id="_1-节点角色" tabindex="-1">1 节点角色 <a class="header-anchor" href="#_1-节点角色" aria-label="Permalink to &quot;1 节点角色&quot;">​</a></h3><p>二阶段提交协议将节点分为：<strong>协调者</strong>和<strong>参与者</strong>，对于者两种角色，当然你也可以理解为<strong>Leader和大头兵</strong>。</p><blockquote><ul><li><strong>协调者Leader</strong>：负责向参与者发送指令，收集参与者反馈，做出提交或者回滚决策</li><li><strong>参与者大头兵</strong>：接收协调者的指令执行事务操作，向协调者反馈操作结果，并继续执行协调者发送的最终指令</li></ul></blockquote><h3 id="_2-两个阶段" tabindex="-1">2 两个阶段 <a class="header-anchor" href="#_2-两个阶段" aria-label="Permalink to &quot;2 两个阶段&quot;">​</a></h3><p>2PC协议分为：<strong>准备阶段</strong>和<strong>提交阶段</strong>。</p><h4 id="_1-准备阶段" tabindex="-1">1 准备阶段 <a class="header-anchor" href="#_1-准备阶段" aria-label="Permalink to &quot;1 准备阶段&quot;">​</a></h4><p>协调者向所有参与者节点发送询问并执行事务的命令，参与者节点收到命令后根据自己的状态执行或者不执行事务，并将动作记录下来，最后将对命令的处理结果反馈给协调者。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101616044.png" alt="image-20221210161650863" style="zoom:67%;"><p><strong>图|准备阶段的三个环节</strong></p><blockquote><p><strong>1询问环节</strong>：协调者向参与者询问，是否准备好可以执行事务，之后协调者开始等待各参与者的响应，这个环节协调者处于阻塞等待状态。</p></blockquote><blockquote><p><strong>2处理环节</strong>：参与者收到协调者的询问后根据自身情况来决定是否执行事务操作，如果参与者执行事务成功，将Undo和Redo信息记入事务日志，但不提交事务；否则直接返回失败。</p></blockquote><blockquote><p><strong>3响应环节</strong>：当参与者成功执行了事务操作，就反馈yes给协调者，表示事务在本地执行；当参与者没有成功执行事务，就反馈no给协调者，表示事务不可以执行提交，这部分反馈对于协调者决策下个阶段起到非常重要的作用。</p></blockquote><h4 id="_2-提交阶段" tabindex="-1">2 提交阶段 <a class="header-anchor" href="#_2-提交阶段" aria-label="Permalink to &quot;2 提交阶段&quot;">​</a></h4><p>协调者根据准备阶段收到的参与者反馈来决定最终提交事务或者中断回滚事务，具体来说，当协调者在准备阶段结束时收到的响应反馈有一个no，那么就中断事务，如果收到的反馈全部是yes就提交事务。</p><p><strong>情况一: 提交事务</strong></p><p>如果在准备阶段结束时，协调者收到了来自<strong>所有参与者的yes反馈</strong>，接下来协调者就会向所有参与者发送提交事务指令，具体的过程如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101615864.png" alt="image-20221210161528794" style="zoom:67%;"><blockquote><p><strong>步骤1</strong>. 协调者向所有参与者发送事务提交消息Commit命令</p></blockquote><blockquote><p><strong>步骤2</strong>. 参与者在收到来自协调者的Commit命令之后，执行事务提交动作，并<strong>释放事务期间所有持有的锁和资源</strong>，这一步很重要</p></blockquote><blockquote><p><strong>步骤3</strong>. 所有参与者在执行本地事务且释放资源完成后，向协调者发送事务提交确认消息ACK</p></blockquote><blockquote><p><strong>步骤4</strong>. 协调者在收到所有参与者的ACK消息后确认完成本次事务</p></blockquote><p><strong>情况二: 回滚事务</strong></p><p>如果在准备阶段结束时，协调者<strong>没有</strong>收到来自所有参与者的yes反馈，接下来协调者就会向所有参与者发送<strong>回滚事务指令</strong>，具体的过程如下：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101615621.png" alt="image-20221210161515539" style="zoom:67%;"><blockquote><p><strong>步骤1.</strong> 协调者向所有参与者发送事务回滚消息Rollback</p></blockquote><blockquote><p><strong>步骤2.</strong> 参与者收到Rollback回滚指令后，根据本地的回滚日志来撤销阶段一执行的事务，并释放事务期间所有持有的锁和资源</p></blockquote><blockquote><p><strong>步骤3.</strong> 所有参与者在完成指令后向协调者回复反馈ACK</p></blockquote><blockquote><p><strong>步骤4.</strong> 协调者在收到所有参与者的ACK确认消息后撤销该事务</p></blockquote><p>通过上面的描述我们知道了，2PC协议通过将节点分为不同的角色，进行两个阶段的处理来执行分布式事务，但是难免要去想2PC协议可以真正解决分布式数据一致问题吗？</p><h2 id="_4-2pc协议的异常分析" tabindex="-1">4. 2PC协议的异常分析 <a class="header-anchor" href="#_4-2pc协议的异常分析" aria-label="Permalink to &quot;4. 2PC协议的异常分析&quot;">​</a></h2><blockquote><p>前面的两个阶段涉及了多次协调者和参与者的网络通信交互，但是我们都知道<strong>网络是不可靠的</strong>，<strong>节点也能出现故障</strong>，因此必须要考虑异常情况下2PC协议是否仍然可以正常工作。</p></blockquote><blockquote><p>故障可以分为很多种：<strong>可恢复机器故障</strong>和<strong>不可恢复机器故障</strong>、<strong>网络正常抖动故障</strong>、<strong>网络分区故障</strong>等多种情况。</p></blockquote><blockquote><p>2PC整个过程交互也很多，<strong>不同阶段发生不同故障造成的结果也是不一样的，<strong>显然</strong>这是个m*n的组合问题</strong>，不同的异常情况会产生不同的结果要完全列出来并分析绝非易事，我们找其中几个重点异常来做分析。</p></blockquote><blockquote><p>从前面的分析我们知道，在<strong>准备阶段本质上是不会提交事务数据的，即使发生故障也可以根据日志来进行回滚，所以不会产生数据不一致的问题</strong>，但是在提交阶段提交事务的情况下由于可能已经有参与者提交了事务数据，因此可能出现数据不一致，这个是要重点分析的阶段。</p></blockquote><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101614779.png" alt="image-20221210161431683" style="zoom:67%;"><p><strong>注：以下所说的参与者故障并非指全部的参与者，而是其中1个或者几个。</strong></p><ul><li><strong>协调者故障&amp;参与者正常</strong></li></ul><blockquote><p>协调者作为系统的领导者角色，由于协调者是单点的在任何过程出现异常都需要重点处理，发生不可恢复的物理故障时，选举出新的协调者来接替之前的工作，这时新协调者就需要向所有参与者询问当前的阶段和处理进度，但是如果只是出现网络分区协调者暂时失联，就可能出现脑裂的情况。</p></blockquote><ul><li><strong>协调者正常&amp;参与者故障</strong></li></ul><blockquote><p>由于协调者做决策需要依据参与者的反馈，出现参与者故障会造成协调者决策困难，如果参与者的故障是可恢复的，在短时间内恢复后则向协调者询问自己需要做什么，从而跟上节奏，如果时间过长或者是不可恢复故障，那么协调者就会回滚本次事务。</p></blockquote><ul><li><strong>协调者故障&amp;参与者故障(重点)</strong></li></ul><blockquote><p>协调者在提交阶段会向所有参与者发送指令，假定在<strong>协调者发送第一条指令之后挂掉</strong>，此时<strong>只有一个参与者接收到了指令并执行后也挂掉了</strong>，<strong>其他参与者并没有收到指令</strong>。</p></blockquote><blockquote><p>新选举出来的协调者询问所有参与节点状态时，如果<strong>已经挂掉的参与者恢复</strong>了，那么状态就明确了commit或者rollback，如果挂掉的<strong>参与者并没有恢复并且已经执行了commit/rollback操作</strong>，那么将会<strong>出现数据不一致</strong>，并且新的协调者由于没有获得足够的信息无法明确当前的状态，其他的参与者在阶段一执行后<strong>产生阻塞</strong>。</p></blockquote><ul><li><strong>网络故障</strong></li></ul><blockquote><p>网络分区是个很棘手的问题，极端情况下可能出现几个分区，不同分区中有独自的参与者和协调者。</p></blockquote><p>综上可知，2PC协议在协调者和参与者都挂掉的时候会出现数据不一致，并且在正常的交互过程中会有阻塞情况，以及协调者单点的问题。</p><h2 id="_5-2pc协议的优缺点" tabindex="-1">5. 2PC协议的优缺点 <a class="header-anchor" href="#_5-2pc协议的优缺点" aria-label="Permalink to &quot;5. 2PC协议的优缺点&quot;">​</a></h2><blockquote><p>2PC协议的原理简单，实现方便，但是存在<strong>同步阻塞</strong>、<strong>单点问题</strong>、<strong>网络分区脑裂</strong>、极端情况<strong>数据不一致</strong>的问题，我们具体展开一下。</p></blockquote><blockquote><p>2PC在准备阶段等待参与者响应反馈时是同步阻塞的，在实际网络中可能会导致长时间阻塞的问题，因为我们无法保证所有参与者网络的顺畅。协调者在整个两阶段中作用很大，但是存在单点问题，一旦出现协调者故障，所有的参与者都将处于阻塞资源无法释放的情况，从而影响其他操作的进行。</p></blockquote><blockquote><p>2PC协议整个过程中基于所有参与者的反馈，但是由于异常情况的存在，在一些时候很难达成一致从而回滚事务，整个策略容错性不强，并且网络分区的存在可能产生脑裂造成分区数据不一致。</p></blockquote><h1 id="浅谈分布式系统一致性问题" tabindex="-1">浅谈分布式系统一致性问题 <a class="header-anchor" href="#浅谈分布式系统一致性问题" aria-label="Permalink to &quot;浅谈分布式系统一致性问题&quot;">​</a></h1><h2 id="_1-为什么要学分布式" tabindex="-1">1.为什么要学分布式 <a class="header-anchor" href="#_1-为什么要学分布式" aria-label="Permalink to &quot;1.为什么要学分布式&quot;">​</a></h2><p>作为后端从业人员，我们在找工作写简历的时候除了高并发经验，一般还会写上自己<strong>熟悉|了解|掌握|精通</strong>分布式系统，所以高并发和分布式大多是成对出现的。</p><p>分布式系统是个多金的知识点，那还不抓紧行动！</p><h2 id="_2-熵增的分布式系统" tabindex="-1">2. 熵增的分布式系统 <a class="header-anchor" href="#_2-熵增的分布式系统" aria-label="Permalink to &quot;2. 熵增的分布式系统&quot;">​</a></h2><p>关于什么是分布式系统，有很多文章介绍，其实这个并不难理解，大白话讲就是：<strong>工厂<strong><strong>活多了一个人撑不住，那就多找些工人一起干，要让这么多人为了</strong></strong>一个目标****干得快干得好，就需要一些规矩和套路，否则就乱了。</strong></p><p>从实践来看分布式系统属于重要的架构模式，对于互联网工程架构的演进，简单提一下为什么会出现分布式系统以及什么是分布式系统：</p><blockquote><p>业务量的迅速增大，普通的单机系统无法满足要求，要么垂直扩展升级机器硬件，要么水平扩展堆廉价服务器，这也是主流可以想到的解决方法，目前来看互联网领域选择了后者-水平扩展。</p></blockquote><blockquote><p>水平扩展机器多机房部署升级服务集群规模来应对业务的增长，也就出现了分布式系统，这些分布式系统中的物理节点可能是<strong>多机房多网络场景部署</strong>的，相互之间通过网络进行<strong>通信和协作</strong>。</p><p>分布式系统就是为了解决巨大业务量和数据量而生的，但是庞大数量的节点来一起正确有序的完成共同的目标是需要理论和实践来锤打的，这也是分布式系统的重点内容。</p></blockquote><p>一般我们常接触的分布式系统包括两大类：分布式存储和分布式计算。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101622835.png" alt="image-20221210162241563" style="zoom:50%;"><p>分布式系统那么多机器要一起协调去完成任务也不是一件容易的事情，所以我们通常认为分布式系统是个<strong>熵</strong>增过程。</p><blockquote><p><strong>熵</strong>是描述一个系统内在混乱程度的物理量，对于一个宏观熵看孤立的系统来说，在没有外力干预做功的前提下，系统内在混乱程度是会不断增加的，也就是熵是增加的。</p><p>为了让系统保持有序就必须对其进行外力干涉，对于分布式系统而言，我们必须使用相应的策略和算法使整个系统保持有序和正确，所以认为分布式系统是个<strong>熵增过程</strong>。</p><p>这个并不难理解，就像我们为了保持房屋整洁，定期必须打扫，要不然就乱成一锅粥了。</p></blockquote><p>如果对于系统不加以控制和干预，系统将自主走向<strong>混乱和无序</strong>。</p><h2 id="_3-分布式一致性问题的理解" tabindex="-1">3.分布式一致性问题的理解 <a class="header-anchor" href="#_3-分布式一致性问题的理解" aria-label="Permalink to &quot;3.分布式一致性问题的理解&quot;">​</a></h2><p><strong>分布式一致性到底是什么一致？</strong></p><p>分布式的一致性可以表现在很多方面，这些都是个性问题，然而无论这些个性问题有多少，任何行为和状态的展示必然是<strong>以数据为基础</strong>的，所以这些个性的一致性问题最终都会映射到一个<strong>共性问题</strong>--<strong>分布式数据的一致性</strong>。</p><p>分布式系统中拥有很多独立的节点，这些节点一般来说可以独立进行存储和计算任务，这两项是最主要的任务类型，本质上<strong>计算和存储</strong>的过程仍然是<strong>围绕数据</strong>展开的，所以最终还是数据一致性。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101622999.png" alt="image-20221210162200730" style="zoom:67%;"><p>在<strong>中心化结构</strong>中，存在管理节点和任务节点的区别，也就是每个节点的权利和义务是不一样的，管理节点可能负责分配任务给下属节点和收集计算结果等，总体承担协调者的角色，任务节点主要是承接任务，这样容易出现管理节点的单点问题。</p><p>在<strong>去中心化的结构</strong>中，各个节点的权利和义务是相同的，尽管没有单独指定领导者，在实际的运行中仍然会选举出领导者和failover动态更新领导者的问题，完全的去中心化系统并不多，相比中心化系统来说，去中心系统更加扁平也更加稳定，像Redis官方集群就是去中心化的实现，任何一个节点的故障都不会带来特别大的问题，因为节点是平等的。</p><p>无论在中心化还是去中心化的分布式系统中，任何一个节点的计算和存储结果都会对其他节点产生影响，这些独立的节点通过基础和特定的网络协议进行协作，从而形成一个整体。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101621532.png" alt="image-20221210162131280" style="zoom:67%;"><h2 id="_4-严格意义的数据一致性" tabindex="-1">4. 严格意义的数据一致性 <a class="header-anchor" href="#_4-严格意义的数据一致性" aria-label="Permalink to &quot;4. 严格意义的数据一致性&quot;">​</a></h2><p>经过前面的一些铺垫，我们开始重点部分的学习-分布式系统数据一致性问题。</p><p>我们必须要有个共识：<strong>严格意义上的分布式数据一致性是不存在的。</strong></p><p>为啥不存在呢？</p><p>在分布式系统中数据存储是多节点主从备份的，一般做成读写分离，当客户端将数据通过主库的代理写入之后，<strong>在极其短暂的瞬间</strong>，主节点的数据是无法复制到从节点的，这个瞬间其他客户端读取到的从库数据都是旧数据。</p><p>聪明的读者盆友们可以体会一下<strong>瞬间</strong>这个词，当然你可以认为这是相对论的范畴，从物理角度去看可能更能体会。</p><p>我们以redis主从节点之间的数据复制来看<strong>同步复制</strong>和<strong>异步复制</strong>场景下的数据一致性问题：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101621897.png" alt="image-20221210162110748" style="zoom:50%;"><p>一般来说，为了保证服务的高可用，主从节点的数据复制是异步的，因为<strong>同步复制延时无法保证</strong>，当然有的场景也是同步复制的，这样整体延时是无法保证的，假如是一主多从就更无法保证了同步复制的延时了。</p><p>所以我们不讨论严苛意义上的数据一致性，而是研究在我们认为可以接受的时间长度下的数据一致性问题，也就是在<strong>自身环境约束下的数据一致性</strong>。</p><p>单机系统的一致性和事务都是比较容易达到的，在分布式系统中由于所有节点的交互都要通过网络来实现，网络必然存在不稳定并且庞大系统中的单节点稳定性也是需要考虑的。</p><p>前面这段话，读起来云里雾里，我想表达的意思是：不要过分把对单机系统中的数据一致性要求照搬到分布式系统中，因为<strong>两者的约束不一样</strong>，我们要合理分析从而让分布式系统的一致性尽量接近单机系统。</p><blockquote><p>solo和团战毕竟是不一样的，典型的《倚天屠龙记》中张无忌要去少林寺救谢逊，但是遇上的少林三位神僧渡厄、渡难、渡劫已经坐禅几十年，三人合一登峰造极，实在太难了，这也是优秀分布式系统的顶峰吧...</p></blockquote><h2 id="_5-cap理论和pacelc理论" tabindex="-1">5.CAP理论和PACELC理论 <a class="header-anchor" href="#_5-cap理论和pacelc理论" aria-label="Permalink to &quot;5.CAP理论和PACELC理论&quot;">​</a></h2><p>我们知道cap理论描述了一致性、可用性、分区容忍性的关系。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101620051.png" alt="image-20221210162049866" style="zoom:33%;"><p>在分布式系统中，由于节点物理分布和网络稳定性等原因，<strong>分区容忍性P是必然存在的</strong>，因此分布式系统必然要建立在分布式网络存在分区P的前提下。</p><p>在<strong>P的基础上我们对于C和A进行选择</strong>，当然并不是说在任何时刻我们都必须C和A二选一，在网络正常的情况下C和A我们也是可以都有的，并且每个系统设计目标也不一样，需要更加实际要求来进行选择。</p><p>分布式系统中P是必然存在的，我们在设计系统之初就<strong>要对C和A做平衡和选择</strong>，在正常的情况下跑出正确的结果是基本要求，在异常情况下仍然可以正常运行是设计重点。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101620769.png" alt="image-20221210162032560" style="zoom:67%;"><p>在分布式系统中，我们使用<strong>PACELC理论比CAP理论更加合适</strong>，因为PACELC理论是CAP理论的扩展，简单来说PACELC理论的表述是这样的：</p><blockquote><p>如果分区partition (P)存在，分布式系统就必须在availability (A) 和consistency (C)之间取得平衡作出选择，否则else (E) 当系统运行在无分区P情况下,系统需要在 latency (L) 和 consistency (C)之间取得平衡。</p></blockquote><p>PACELC理论比CAP理论更适合分布式系统，它完全展现了出现网络分区和正常情况下的取舍平衡问题，特别地引入了<strong>L时延因素</strong>，来对一致性C进行说明，也就是我们常说的<strong>强一致性和弱一致性</strong>。</p><p>强一致性不必多说，对主从数据的一致性要求很高，一般会牺牲可用性来保证，弱一致性又可以分为最终一致性/会话一致性/单调读一致性/单调写一致性等情况，从实用的角度来说我们重点关注弱一致性的<strong>最终一致性</strong>情况即可。</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101620382.png" alt="image-20221210162013066" style="zoom:67%;"><h2 id="_6-分布式和base理论" tabindex="-1">6.分布式和BASE理论 <a class="header-anchor" href="#_6-分布式和base理论" aria-label="Permalink to &quot;6.分布式和BASE理论&quot;">​</a></h2><p>我们知道由于网络稳定性原因，分布式系统出现<strong>网络分区是必须要考虑的问题</strong>，在一般的互联网场景中我们选择最终一致性来保证服务的高可用，也就是允许一段时间L的数据不一致，经过数据复制和同步后最终达到一致。</p><p>我们看下BASE理论，这是我们理解分布式系统一致性的重要理论基础:</p><blockquote><p>BASE是基本可用（<strong>Basically Available</strong>）、软状态（<strong>Soft state</strong>）和最终一致性（<strong>Eventually consistent</strong>）三个短语的缩写。</p><p><strong>BA基本可用</strong>是指:系统在绝大部分时间应处于可用状态,允许出现故障损失部分可用性,但保证核心可用。</p><p><strong>S软状态</strong>是指:数据状态不要求在任何时刻都保持一致,允许存在中间状态,而该状态不影响系统可用性。</p><p><strong>E最终一致性</strong>是指:软状态前提下，经过一定时间后,这些数据最终能达到一致性状态。</p></blockquote><h2 id="_7-cap-base-acid的关系" tabindex="-1">7.CAP&amp;BASE&amp;ACID的关系 <a class="header-anchor" href="#_7-cap-base-acid的关系" aria-label="Permalink to &quot;7.CAP&amp;BASE&amp;ACID的关系&quot;">​</a></h2><p>CAP理论说明了分布式系统中一致性C 、可用性A、分区容错性P之间的制约关系。</p><p>BASE理论和ACID理论可以看做是对CAP理论中三要素进行取舍后的某种情况，也是在单机系统和分布式系统中适用的情况，三者的关系如图：</p><img src="https://edu-8673.oss-cn-beijing.aliyuncs.com/img2022.12.30/202212101619922.png" alt="image-20221210161928394" style="zoom:50%;"><h1 id="分布式系统中的补偿机制设计问题" tabindex="-1">分布式系统中的补偿机制设计问题 <a class="header-anchor" href="#分布式系统中的补偿机制设计问题" aria-label="Permalink to &quot;分布式系统中的补偿机制设计问题&quot;">​</a></h1><p>我们知道，应用系统在分布式的情况下，在通信时会有着一个显著的问题，即一个业务流程往往需要组合一组服务，且单单一次通信可能会经过 DNS 服务，网卡、交换机、路由器、负载均衡等设备，而这些服务于设备都不一定是一直稳定的，在数据传输的整个过程中，只要任意一个环节出错，都会导致问题的产生。</p><p>这样的事情在微服务下就更为明显了，因为业务需要在一致性上的保证。也就是说，如果一个步骤失败了，要么不断重试保证所有的步骤都成功，要么回滚到以前的服务调用。</p><p>因此我们可以对业务补偿的过程进行一个定义，即当某个操作发生了异常时，如何通过内部机制将这个异常产生的「不一致」状态消除掉。</p><h2 id="文章目录" tabindex="-1">文章目录 <a class="header-anchor" href="#文章目录" aria-label="Permalink to &quot;文章目录&quot;">​</a></h2><ul><li><p>一、关于业务补偿机制</p></li><li><ul><li>1、什么是业务补偿</li><li>2、业务补偿设计的实现方式</li></ul></li><li><p>二、关于回滚</p></li><li><ul><li>1、显示回滚</li><li>2、回滚的实现方式</li></ul></li><li><p>三、关于重试</p></li><li><ul><li>1、重试的使用场景</li><li>2、重试策略</li><li>3、重试时的注意事项</li></ul></li><li><p>四、业务补偿机制的注意事项</p></li><li><ul><li>1、ACID 还是 BASE</li><li>2、业务补偿设计的注意事项</li></ul></li></ul><h2 id="关于业务补偿机制" tabindex="-1">关于业务补偿机制 <a class="header-anchor" href="#关于业务补偿机制" aria-label="Permalink to &quot;关于业务补偿机制&quot;">​</a></h2><h3 id="_1、什么是业务补偿" tabindex="-1">1、什么是业务补偿 <a class="header-anchor" href="#_1、什么是业务补偿" aria-label="Permalink to &quot;1、什么是业务补偿&quot;">​</a></h3><p>我们知道，应用系统在分布式的情况下，在通信时会有着一个显著的问题，即一个业务流程往往需要组合一组服务，且单单一次通信可能会经过 DNS 服务，网卡、交换机、路由器、负载均衡等设备，而这些服务于设备都不一定是一直稳定的，在数据传输的整个过程中，只要任意一个环节出错，都会导致问题的产生。</p><p>这样的事情在微服务下就更为明显了，因为业务需要在一致性上的保证。也就是说，如果一个步骤失败了，要么不断重试保证所有的步骤都成功，要么回滚到以前的服务调用。</p><p>因此我们可以对业务补偿的过程进行一个定义，即当某个操作发生了异常时，如何通过内部机制将这个异常产生的「不一致」状态消除掉。</p><h3 id="_2、业务补偿设计的实现方式" tabindex="-1">2、业务补偿设计的实现方式 <a class="header-anchor" href="#_2、业务补偿设计的实现方式" aria-label="Permalink to &quot;2、业务补偿设计的实现方式&quot;">​</a></h3><p>业务补偿设计的实现方式主要可分为两种：</p><ul><li><strong>回滚（事务补偿）</strong>，逆向操作，回滚业务流程，意味着放弃，当前操作必然会失败；</li><li><strong>重试</strong>，正向操作，努力地把一个业务流程执行完成，代表着还有成功的机会。</li></ul><p>一般来说，业务的事务补偿都是需要一个工作流引擎的。这个工作流引擎把各式各样的服务给串联在一起，并在工作流上做相应的业务补偿，整个过程设计成为最终一致性的。</p><blockquote><p>Ps：因为「补偿」已经是一个额外流程了，既然能够走这个额外流程，说明时效性并不是第一考虑的因素。所以做补偿的核心要点是：宁可慢，不可错。</p></blockquote><h2 id="关于回滚" tabindex="-1">关于回滚 <a class="header-anchor" href="#关于回滚" aria-label="Permalink to &quot;关于回滚&quot;">​</a></h2><p>“回滚” 是指当程序或数据出错时，将程序或数据恢复到最近的一个正确版本的行为。在分布式业务补偿设计到的回滚则是通过事务补偿的方式，回到服务调用以前的状态。</p><h3 id="_1、显示回滚" tabindex="-1">1、显示回滚 <a class="header-anchor" href="#_1、显示回滚" aria-label="Permalink to &quot;1、显示回滚&quot;">​</a></h3><p>回滚一般可分为 2 种模式：</p><ul><li><strong>显式回滚</strong>；调用逆向接口，进行上一次操作的反操作，或者取消上一次还没有完成的操作（须锁定资源）；</li><li><strong>隐式回滚</strong>：隐式回滚意味着这个回滚动作你不需要进行额外处理，往往是由下游提供了失败处理机制的。</li></ul><p>最常见的就是「显式回滚」。这个方案无非就是做 2 个事情：</p><ul><li>首先要确定失败的步骤和状态，从而确定需要回滚的范围。一个业务的流程，往往在设计之初就制定好了，所以确定回滚的范围比较容易。但这里唯一需要注意的一点就是：如果在一个业务处理中涉及到的服务并不是都提供了「回滚接口」，那么在编排服务时应该把提供「回滚接口」的服务放在前面，这样当后面的工作服务错误时还有机会「回滚」。</li><li>其次要能提供「回滚」操作使用到的业务数据。「回滚」时提供的数据越多，越有益于程序的健壮性。因为程序可以在收到「回滚」操作的时候可以做业务的检查，比如检查账户是否相等，金额是否一致等等。</li></ul><h3 id="_2、回滚的实现方式" tabindex="-1">2、回滚的实现方式 <a class="header-anchor" href="#_2、回滚的实现方式" aria-label="Permalink to &quot;2、回滚的实现方式&quot;">​</a></h3><p>对于跨库的事务，比较常见的解决方案有：两阶段提交、三阶段提交（ACID）但是这 2 种方式，在高可用的架构中一般都不可取，因为跨库锁表会消耗很大的性能。</p><p>高可用的架构中一般不会要求强一致性，只要达到最终的一致性就可以了。可以考虑：事务表、消息队列、补偿机制、TCC 模式（占位 / 确认或取消）、Sagas模式（拆分事务 + 补偿机制）来实现最终的一致性。</p><h2 id="关于重试" tabindex="-1">关于重试 <a class="header-anchor" href="#关于重试" aria-label="Permalink to &quot;关于重试&quot;">​</a></h2><p>“重试” 的语义是我们认为这个故障是暂时的，而不是永久的，所以，我们会去重试。这个操作最大的好处就是不需要提供额外的逆向接口。这对于代码的维护和长期开发的成本有优势，而且业务是变化的。逆向接口也需要变化。所以更多时候可以考虑重试。</p><h3 id="_1、重试的使用场景" tabindex="-1">1、重试的使用场景 <a class="header-anchor" href="#_1、重试的使用场景" aria-label="Permalink to &quot;1、重试的使用场景&quot;">​</a></h3><p>相较于回滚，重试使用的场景要少一些：下游系统返回请求超时，被限流中等临时状态的时候，我们就可以考虑重试了。而如果是返回余额不足，无权限的明确业务错误，就不需要重试。一些中间件或者 RPC 框架，返回 503，404 这种没有预期恢复时间的错误，也不需要重试了。</p><h3 id="_2、重试策略" tabindex="-1">2、重试策略 <a class="header-anchor" href="#_2、重试策略" aria-label="Permalink to &quot;2、重试策略&quot;">​</a></h3><p>重试的时间和重试的次数。这种在不同的情况下要有不同的考量，主流的重试策略主要是以下几种：</p><p><strong>策略 1 - 立即重试</strong>：有时候故障是暂时性的，可能因为网络数据包冲突或者硬件组件高峰流量等事件造成的，在这种情况下，适合立即重试的操作。不过立即重试的操作不应该超过一次，如果立即重试失败，应该改用其他策略；</p><p><strong>策略 2 - 固定间隔</strong>：这个很好理解，比如每隔 5 分钟重试一次。PS：策略 1 和策略 2 多用于前端系统的交互操作中；</p><p><strong>策略 3 - 增量间隔</strong>：每一次的重试间隔时间增量递增。比如，第一次 0 秒、第二次 5 秒、第三次 10 秒这样，使得失败次数越多的重试请求优先级排到越后面，给新进入的重试请求让路；</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">return (retryCount - 1) * incrementInterval;</span></span></code></pre></div><p><strong>策略 4 - 指数间隔：</strong> 每一次的重试间隔呈指数级增加。和增量间隔一样，都是想让失败次数越多的重试请求优先级排到越后面，只不过这个方案的增长幅度更大一些；</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">return 2 ^ retryCount;</span></span></code></pre></div><p><strong>策略 5 - 全抖动：</strong> 在递增的基础上，增加随机性（可以把其中的指数增长部分替换成增量增长。）适用于将某一时刻集中产生的大量重试请求进行压力分散的场景；</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">return random(0 , 2 ^ retryCount);</span></span></code></pre></div><p><strong>策略 6 - 等抖动：</strong> 在「指数间隔」和「全抖动」之间寻求一个中庸的方案，降低随机性的作用。适用场景和「全抖动」一样。</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight"><code><span class="line"><span style="color:#A6ACCD;">int baseNum = 2 ^ retryCount;</span></span>
<span class="line"><span style="color:#A6ACCD;">return baseNum + random(0 , baseNum);</span></span></code></pre></div><p>策略 - 3、4、5、6 的表现情况大致是这样（x轴为重试次数）：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/eQPyBffYbuflQ59JYZ6ZRnXibicILwmhJhAeuzNLuEg2U3FOyrXuDTicp1TsxAZUoGibtlnk9iaypEd28ibuInric95KA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt="图片"></p><h3 id="_3、重试时的注意事项" tabindex="-1">3、重试时的注意事项 <a class="header-anchor" href="#_3、重试时的注意事项" aria-label="Permalink to &quot;3、重试时的注意事项&quot;">​</a></h3><p>首先对于需要重试的接口，是需要做成幂等性的，即不能因为服务的多次调用而导致业务数据的累计增加或减少。</p><p>满足「幂等性」其实就是需要想办法识别重复的请求，并且将其过滤掉。思路就是：</p><ul><li>给每个请求定义一个唯一标识。</li><li>在进行「重试」的时候判断这个请求是否已经被执行或者正在被执行，如果是则抛弃该请求。</li></ul><blockquote><p>Ps：此外重试特别适合在高负载情况下被降级，当然也应当受到限流和熔断机制的影响。当重试的“矛”与限流和熔断的“盾”搭配使用，效果才是最好。</p></blockquote><h2 id="业务补偿机制的注意事项" tabindex="-1">业务补偿机制的注意事项 <a class="header-anchor" href="#业务补偿机制的注意事项" aria-label="Permalink to &quot;业务补偿机制的注意事项&quot;">​</a></h2><h3 id="_1、acid-还是-base" tabindex="-1">1、ACID 还是 BASE <a class="header-anchor" href="#_1、acid-还是-base" aria-label="Permalink to &quot;1、ACID 还是 BASE&quot;">​</a></h3><p>ACID 和 BASE 是分布式系统中两种不同级别的一致性理论，在分布式系统中，ACID有更强的一致性，但可伸缩性非常差，仅在必要时使用；BASE的一致性较弱，但有很好的可伸缩性，还可以异步批量处理；大多数分布式事务适合 BASE。</p><p>而在重试或回滚的场景下，我们一般不会要求强一致性，只要保证最终一致性就可以了！</p><h3 id="_2、业务补偿设计的注意事项" tabindex="-1">2、业务补偿设计的注意事项 <a class="header-anchor" href="#_2、业务补偿设计的注意事项" aria-label="Permalink to &quot;2、业务补偿设计的注意事项&quot;">​</a></h3><p>业务补偿设计的注意事项：</p><ul><li>因为要把一个业务流程执行完成，需要这个流程中所涉及的服务方支持幂等性。并且在上游有重试机制；</li><li>我们需要小心维护和监控整个过程的状态，所以，千万不要把这些状态放到不同的组件中，最好是一个业务流程的控制方来做这个事，也就是一个工作流引擎。所以，这个工作流引擎是需要高可用和稳定的；</li><li>补偿的业务逻辑和流程不一定非得是严格反向操作。有时候可以并行，有时候，可能会更简单。总之，设计业务正向流程的时候，也需要设计业务的反向补偿流程；</li><li>我们要清楚地知道，业务补偿的业务逻辑是强业务相关的，很难做成通用的；</li><li>下层的业务方最好提供短期的资源预留机制。就像电商中的把货品的库存预先占住等待用户在 15 分钟内支付。如果没有收到用户的支付，则释放库存。然后回滚到之前的下单操作，等待用户重新下单。</li></ul></div></div></main><footer class="VPDocFooter" data-v-6b87e69f data-v-37656e44><!--[--><!--]--><!----><nav class="prev-next" data-v-37656e44><div class="pager" data-v-37656e44><a class="pager-link prev" href="/notebook/2%E3%80%81%E6%95%B0%E6%8D%AE%E5%BA%93/Neo4j.html" data-v-37656e44><span class="desc" data-v-37656e44>Previous page</span><span class="title" data-v-37656e44>Neo4j</span></a></div><div class="pager" data-v-37656e44><a class="pager-link next" href="/notebook/4%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%BF%85%E5%A4%87/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81.html" data-v-37656e44><span class="desc" data-v-37656e44>Next page</span><span class="title" data-v-37656e44>分布式锁</span></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"2、数据库_mysql_mysql面试_基础.md\":\"40da680a\",\"1、学前端_5、小程序_小程序项目.md\":\"60a1629b\",\"1、学前端_4、node_知识篇.md\":\"a7fb500e\",\"1、学前端_2、js_ts_es6 进阶.md\":\"6d07ba10\",\"1、学前端_3、vue_vue3_vue3进阶.md\":\"7ac622b4\",\"5、运维_jenkins.md\":\"929081f8\",\"1、学前端_2、js_ts_typescript.md\":\"875a4aa4\",\"2、数据库_mysql_mysql核心_设计.md\":\"7faf46d1\",\"2、数据库_mysql_mysql核心_基础.md\":\"d8e97f3e\",\"1、学前端_1、html_css_html基础.md\":\"7584d076\",\"1、学前端_5、专题篇_问题篇.md\":\"e893aaa2\",\"2、数据库_mysql_mysql面试_进阶.md\":\"f934806d\",\"3、springboot_运维_原理.md\":\"f4a39db6\",\"2、数据库_influxdb.md\":\"6e1711e1\",\"3、springboot_新特性.md\":\"cdf3e307\",\"mybatis_mybatisplus_jpa.md\":\"8e41681b\",\"1、学前端_5、小程序_小程序优化.md\":\"a2185198\",\"2、数据库_redis_redis基础.md\":\"856df0e0\",\"linux_实用脚本.md\":\"f2299dd5\",\"4、微服务_必备_分布式基础.md\":\"d49863d5\",\"2、数据库_redis_redis优化.md\":\"e66ae32f\",\"4、微服务_springsecurity_进阶篇.md\":\"235a8e9e\",\"5、运维_chatgpt.md\":\"10db3823\",\"2、数据库_mysql_分库分表.md\":\"e1c8a095\",\"start.md\":\"9bc1ff8d\",\"5、运维_github.md\":\"2ec6c735\",\"java学前端_css.md\":\"f11b47f0\",\"1、学前端_5、专题篇_知识篇.md\":\"a463ed8d\",\"linux_软件部署.md\":\"d6722925\",\"2、数据库_neo4j.md\":\"97ad22ac\",\"team.md\":\"ce467a6a\",\"nginx_实战篇.md\":\"7785486e\",\"index.md\":\"8c3ec167\",\"计算机基础_计算机网络_网络基础.md\":\"7a54a85d\",\"1、学前端_4、node_进阶篇.md\":\"60f6db69\",\"java_java集合.md\":\"a049b313\",\"1、学前端_3、vue_vue3_vue3高级.md\":\"614d1516\",\"1、学前端_5、小程序_微信小程序.md\":\"9a4be771\",\"5、运维_netty.md\":\"12ca0278\",\"2、数据库_mysql_mysql核心_运维.md\":\"83f97c16\",\"idea_vs code.md\":\"afdcb593\",\"java学前端_vue3_组件.md\":\"1086884e\",\"idea_chrome.md\":\"4a32afbc\",\"云原生_k8s.md\":\"db58e65a\",\"2、数据库_mysql_mysql核心_进阶.md\":\"61d16dff\",\"ssm_springbatch.md\":\"f799ab4a\",\"三高_分布式.md\":\"db1b8a1b\",\"2、数据库_elasticsearch_1、es基础.md\":\"04d17448\",\"linux_linux基础.md\":\"4b0bf394\",\"idea_idea插件.md\":\"fa86e45a\",\"可视化 _ 监控_可视化大屏.md\":\"004553bd\",\"2、数据库_mongodb_整合.md\":\"3c47d7f4\",\"4、微服务_springsecurity_基础篇.md\":\"534a3401\",\"4、微服务_进阶.md\":\"69095c58\",\"计算机基础_计算机基础_操作系统.md\":\"0f75d113\",\"可视化 _ 监控_zabbix.md\":\"71f2270e\",\"nginx_基础篇.md\":\"c7d8bb50\",\"1、学前端_4、node_项目实战.md\":\"bc5065b8\",\"2、数据库_redis_redis原理.md\":\"5cedf685\",\"可视化 _ 监控_监控基础.md\":\"ac56ce4d\",\"三高_高并发.md\":\"ea9ffc99\",\"2、数据库_redis_redis高级.md\":\"1d5872f6\",\"1、学前端_4、node_基础篇.md\":\"581cc13a\",\"2、数据库_mongodb_基础.md\":\"fb7a0a29\",\"idea_idea基础.md\":\"6f2f9638\",\"4、微服务_必备_sentinel.md\":\"2edfbf6c\",\"2、数据库_elasticsearch_3、es高级.md\":\"ef146606\",\"1、学前端_3、vue_vue3_vue3新语法.md\":\"8afd5409\",\"消息中间件_canal.md\":\"3949163c\",\"ssm_maven.md\":\"2c5e12ed\",\"4、微服务_springsecurity_高级篇.md\":\"882d3ff3\",\"linux_linux进阶.md\":\"188ef7b4\",\"计算机基础_设计模式_uml.md\":\"634ba256\",\"计算机基础_算法_leetcode.md\":\"77162fb9\",\"项目实战_小兔鲜_进阶篇1.md\":\"17c52c81\",\"1、学前端_2、js_ts_es6 基础.md\":\"fda3f18b\",\"项目实战_小兔鲜_进阶篇2.md\":\"a0f23006\",\"软件测试_测试基础.md\":\"8c1060cd\",\"2、数据库_redis_本地缓存.md\":\"00617fe6\",\"nginx_面试篇.md\":\"e3fb373a\",\"mybatis_mybatisplus_mybatis.md\":\"9239e0ad\",\"linux_shell.md\":\"ae53d83b\",\"2、数据库_mysql_mysql核心_优化.md\":\"36230425\",\"项目实战_项目推荐.md\":\"f9d97630\",\"mybatis_mybatisplus_mybatisplus.md\":\"0030fd35\",\"项目实战_百度地图_进阶篇.md\":\"c8b93267\",\"三高_高可用.md\":\"323840c5\",\"java_java新特性.md\":\"22abf56d\",\"软件测试_压力测试.md\":\"9ab44440\",\"java学前端_html_js.md\":\"e0fcd240\",\"2、数据库_redis_redis实战.md\":\"d6daeeab\",\"nginx_进阶篇.md\":\"e6b63195\",\"三高_秒杀.md\":\"3878bb64\",\"5、运维_git.md\":\"0264925c\",\"java_java进阶.md\":\"e79cb5b4\",\"并发 _ 多线程_基础篇.md\":\"7adbfac5\",\"项目实战_百度地图_基础篇.md\":\"8afa5954\",\"java学前端_react.md\":\"3ec827dd\",\"1、学前端_1、html_css_css基础.md\":\"01b56712\",\"项目实战_小兔鲜_基础篇.md\":\"646f5df5\",\"1、学前端_2、js_ts_js 基础.md\":\"cb13e36f\",\"可视化 _ 监控_监控进阶.md\":\"0cdbc292\",\"计算机基础_设计模式_基础篇.md\":\"51617287\",\"计算机基础_数据结构_基础篇.md\":\"b2bfd8d4\",\"项目实战_苍穹外卖_进阶篇.md\":\"48415e41\",\"ssm_spring.md\":\"ab514659\",\"消息中间件_rabbitmq.md\":\"45b1eb28\",\"1、学前端_1、html_css_网页进阶.md\":\"db998248\",\"消息中间件_kafka.md\":\"b747dabf\",\"云原生_docker.md\":\"983c7ba7\",\"4、微服务_必备_分布式锁.md\":\"5af1cf8d\",\"消息中间件_rocketmq.md\":\"d441da85\",\"项目实战_黑马头条_基础篇.md\":\"b05af3a6\",\"ssm_springmvc.md\":\"81b9714f\",\"项目实战_支付.md\":\"1d7407dd\",\"项目实战_黑马头条_进阶篇2.md\":\"bff0015b\",\"项目实战_黑马头条_进阶篇.md\":\"19f18388\",\"java学前端_vue2_组件.md\":\"58c6b1df\",\"3、springboot_基础篇.md\":\"529c66f4\",\"3、springboot_应用篇.md\":\"8b92aa61\",\"项目实战_黑马头条_高级篇.md\":\"227c08c1\",\"1、学前端_5、小程序_uniapp.md\":\"71a282b4\",\"项目实战_云尚办公_基础篇.md\":\"1fe188ba\",\"并发 _ 多线程_并发完善.md\":\"26619c46\",\"1、学前端_2、js_ts_js 进阶.md\":\"657dfb8f\",\"java_java高级.md\":\"23782d1a\",\"java_java基础.md\":\"86d67c77\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"VitePress\",\"description\":\"A VitePress site\",\"base\":\"/notebook/\",\"head\":[],\"appearance\":true,\"themeConfig\":{\"algolia\":{\"appId\":\"DW7O63I9IR\",\"apiKey\":\"f8ed758cdb288a8b06542bc35923c1a1\",\"indexName\":\"notebook\"},\"sidebar\":[{\"text\":\"Java\",\"collapsed\":true,\"items\":[{\"text\":\"Java基础\",\"link\":\"/Java/Java基础\"},{\"text\":\"Java新特性\",\"link\":\"/Java/Java新特性\"},{\"text\":\"Java进阶\",\"link\":\"/Java/Java进阶\"},{\"text\":\"Java集合\",\"link\":\"/Java/Java集合\"},{\"text\":\"Java高级\",\"link\":\"/Java/Java高级\"}]},{\"text\":\"Linux\",\"collapsed\":true,\"items\":[{\"text\":\"Linux基础\",\"link\":\"/Linux/Linux基础\"},{\"text\":\"Linux新特性\",\"link\":\"/Linux/Linux进阶\"},{\"text\":\"Shell脚本\",\"link\":\"/Linux/Shell\"},{\"text\":\"实用脚本\",\"link\":\"/Linux/实用脚本\"},{\"text\":\"软件部署\",\"link\":\"/Linux/软件部署\"}]},{\"text\":\"Nginx\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/Nginx/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/Nginx/进阶篇\"},{\"text\":\"实战篇\",\"link\":\"/Nginx/实战篇\"},{\"text\":\"面试篇\",\"link\":\"/Nginx/面试篇\"}]},{\"text\":\"SSM\",\"collapsed\":true,\"items\":[{\"text\":\"Maven\",\"link\":\"/SSM/Maven\"},{\"text\":\"Spring\",\"link\":\"/SSM/Spring\"},{\"text\":\"SpringMVC\",\"link\":\"/SSM/SpringMVC\"},{\"text\":\"SpringBatch\",\"link\":\"/SSM/SpringBatch\"}]},{\"text\":\"SpringBoot\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/3、SpringBoot/基础篇\"},{\"text\":\"应用篇\",\"link\":\"/3、SpringBoot/应用篇\"},{\"text\":\"新特性\",\"link\":\"/3、SpringBoot/新特性\"},{\"text\":\"运维&原理\",\"link\":\"/3、SpringBoot/运维&原理\"}]},{\"text\":\"SpringCloud\",\"collapsed\":true,\"items\":[{\"text\":\"SpringCloud\",\"link\":\"/4、微服务/进阶\"},{\"text\":\"Sentinel\",\"link\":\"/4、微服务/必备/Sentinel\"}]},{\"text\":\"SpringSecurity\",\"collapsed\":true,\"items\":[{\"text\":\"SpringSecurity基础篇\",\"link\":\"/4、微服务/SpringSecurity/基础篇\"},{\"text\":\"SpringSecurity进阶篇\",\"link\":\"/4、微服务/SpringSecurity/进阶篇\"},{\"text\":\"SpringSecurity高级篇\",\"link\":\"/4、微服务/SpringSecurity/高级篇\"}]},{\"text\":\"Mybatis & MybatisPlus\",\"collapsed\":true,\"items\":[{\"text\":\"Mybatis\",\"link\":\"/Mybatis&MybatisPlus/Mybatis\"},{\"text\":\"MybatisPlus\",\"link\":\"/Mybatis&MybatisPlus/MybatisPlus\"},{\"text\":\"JPA\",\"link\":\"/Mybatis&MybatisPlus/JPA\"}]},{\"text\":\"Git & ChatGPT\",\"collapsed\":true,\"items\":[{\"text\":\"Git\",\"link\":\"/5、运维/Git\"},{\"text\":\"Github\",\"link\":\"/5、运维/Github\"},{\"text\":\"ChatGPT\",\"link\":\"/5、运维/ChatGPT\"},{\"text\":\"Jenkins\",\"link\":\"/5、运维/Jenkins\"},{\"text\":\"Netty\",\"link\":\"/5、运维/Netty\"}]},{\"text\":\"数据库\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL\",\"collapsed\":true,\"items\":[{\"text\":\"MySQL基础\",\"link\":\"/2、数据库/MySQL/MySQL核心/基础\"},{\"text\":\"MySQL进阶\",\"link\":\"/2、数据库/MySQL/MySQL核心/进阶\"},{\"text\":\"MySQL优化\",\"link\":\"/2、数据库/MySQL/MySQL核心/优化\"},{\"text\":\"MySQL设计\",\"link\":\"/2、数据库/MySQL/MySQL核心/设计\"},{\"text\":\"MySQL运维\",\"link\":\"/2、数据库/MySQL/MySQL核心/运维\"},{\"text\":\"分库分表\",\"link\":\"/2、数据库/MySQL/分库分表\"}]},{\"text\":\"Redis\",\"collapsed\":true,\"items\":[{\"text\":\"Redis基础\",\"link\":\"/2、数据库/Redis/Redis基础\"},{\"text\":\"Redis优化\",\"link\":\"/2、数据库/Redis/Redis优化\"},{\"text\":\"Redis原理\",\"link\":\"/2、数据库/Redis/Redis原理\"},{\"text\":\"Redis高级\",\"link\":\"/2、数据库/Redis/Redis高级\"},{\"text\":\"Redis实战\",\"link\":\"/2、数据库/Redis/Redis实战\"},{\"text\":\"本地缓存\",\"link\":\"/2、数据库/Redis/本地缓存\"}]},{\"text\":\"MongoDB\",\"collapsed\":true,\"items\":[{\"text\":\"MongoDB基础\",\"link\":\"/2、数据库/MongoDB/基础\"},{\"text\":\"MongoDB进阶\",\"link\":\"/2、数据库/MongoDB/整合\"}]},{\"text\":\"ElasticSearch\",\"collapsed\":true,\"items\":[{\"text\":\"ES基础\",\"link\":\"/2、数据库/ElasticSearch/1、ES基础\"},{\"text\":\"ES高级\",\"link\":\"/2、数据库/ElasticSearch/3、ES高级\"}]},{\"text\":\"InfluxDB\",\"link\":\"/2、数据库/influxdb\"},{\"text\":\"Neo4j\",\"link\":\"/2、数据库/Neo4j\"}]},{\"text\":\"高并发 & 秒杀 & 分布式\",\"collapsed\":true,\"items\":[{\"text\":\"分布式理论\",\"link\":\"/三高/分布式\"},{\"text\":\"分布式锁\",\"link\":\"/4、微服务/必备/分布式锁\"},{\"text\":\"秒杀\",\"link\":\"/三高/秒杀\"},{\"text\":\"高可用\",\"link\":\"/三高/高可用\"},{\"text\":\"高并发\",\"link\":\"/三高/高并发\"}]},{\"text\":\"云原生\",\"collapsed\":true,\"items\":[{\"text\":\"Docker\",\"link\":\"/云原生/Docker\"},{\"text\":\"K8S\",\"link\":\"/云原生/K8S\"}]},{\"text\":\"可视化 & 监控\",\"collapsed\":true,\"items\":[{\"text\":\"监控基础\",\"link\":\"/可视化 & 监控/监控基础\"},{\"text\":\"监控进阶\",\"link\":\"/可视化 & 监控/监控进阶\"},{\"text\":\"可视化大屏\",\"link\":\"/可视化 & 监控/可视化大屏\"},{\"text\":\"Zabbix\",\"link\":\"/可视化 & 监控/Zabbix\"}]},{\"text\":\"学前端\",\"collapsed\":true,\"items\":[{\"text\":\"HTML+CSS\",\"collapsed\":true,\"items\":[{\"text\":\"HTML基础\",\"link\":\"/1、学前端/1、HTML+CSS/HTML基础\"},{\"text\":\"CSS基础\",\"link\":\"/1、学前端/1、HTML+CSS/CSS基础\"},{\"text\":\"网页进阶\",\"link\":\"/1、学前端/1、HTML+CSS/网页进阶\"}]},{\"text\":\"JS+TS\",\"collapsed\":true,\"items\":[{\"text\":\"JS基础\",\"link\":\"/1、学前端/2、JS+TS/JS 基础\"},{\"text\":\"JS进阶\",\"link\":\"/1、学前端/2、JS+TS/JS 进阶\"},{\"text\":\"ES6基础\",\"link\":\"/1、学前端/2、JS+TS/ES6 基础\"},{\"text\":\"ES6进阶\",\"link\":\"/1、学前端/2、JS+TS/ES6 进阶\"},{\"text\":\"TS基础\",\"link\":\"/1、学前端/2、JS+TS/TypeScript\"}]},{\"text\":\"NodeJS\",\"collapsed\":true,\"items\":[{\"text\":\"Node基础\",\"link\":\"/1、学前端/4、Node/基础篇\"},{\"text\":\"Node进阶\",\"link\":\"/1、学前端/4、Node/进阶篇\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/4、Node/项目实战\"}]},{\"text\":\"Vue\",\"collapsed\":true,\"items\":[{\"text\":\"Vue3进阶\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3进阶\"},{\"text\":\"Vue3高级\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3高级\"},{\"text\":\"Vue3新语法\",\"link\":\"/1、学前端/3、Vue/Vue3/Vue3新语法\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/3、Vue/Vue2/Vue2项目\"}]},{\"text\":\"小程序\",\"collapsed\":true,\"items\":[{\"text\":\"小程序基础\",\"link\":\"/1、学前端/5、小程序/微信小程序\"},{\"text\":\"小程序优化\",\"link\":\"/1、学前端/5、小程序/小程序优化\"},{\"text\":\"uniapp\",\"link\":\"/1、学前端/5、小程序/uniapp\"},{\"text\":\"项目实战\",\"link\":\"/1、学前端/5、小程序/小程序项目\"}]}]},{\"text\":\"计算机基础\",\"collapsed\":true,\"items\":[{\"text\":\"数据结构\",\"link\":\"/计算机基础/数据结构/基础篇\"},{\"text\":\"操作系统\",\"link\":\"/计算机基础/计算机基础/操作系统\"},{\"text\":\"设计模式\",\"link\":\"/计算机基础/设计模式/基础篇\"},{\"text\":\"计算机网络\",\"link\":\"/计算机基础/计算机网络/网络基础\"},{\"text\":\"UML\",\"link\":\"/计算机基础/设计模式/UML\"},{\"text\":\"LeetCode\",\"link\":\"/计算机基础/算法/LeetCode\"}]},{\"text\":\"项目实战\",\"collapsed\":true,\"items\":[{\"text\":\"云尚办公\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/云尚办公/基础篇\"}]},{\"text\":\"小兔鲜\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/小兔鲜/基础篇\"},{\"text\":\"进阶篇1\",\"link\":\"/项目实战/小兔鲜/进阶篇1\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/小兔鲜/进阶篇2\"}]},{\"text\":\"地图\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/百度地图/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/百度地图/进阶篇\"}]},{\"text\":\"苍穹外卖\",\"collapsed\":true,\"items\":[{\"text\":\"进阶篇\",\"link\":\"/项目实战/苍穹外卖/进阶篇\"}]},{\"text\":\"黑马头条\",\"collapsed\":true,\"items\":[{\"text\":\"基础篇\",\"link\":\"/项目实战/黑马头条/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/项目实战/黑马头条/进阶篇\"},{\"text\":\"进阶篇2\",\"link\":\"/项目实战/黑马头条/进阶篇2\"},{\"text\":\"高级篇\",\"link\":\"/项目实战/黑马头条/高级篇\"}]},{\"text\":\"支付\",\"link\":\"/项目实战/支付\"},{\"text\":\"项目推荐\",\"link\":\"/项目实战/项目推荐\"}]},{\"text\":\"团队成员\",\"link\":\"/team\"}],\"siteTitle\":\"任硕的文档\",\"logo\":\"/Vue.png\",\"nav\":[{\"text\":\"Java学前端\",\"items\":[{\"items\":[{\"text\":\"HTML+JS\",\"link\":\"/Java学前端/HTML+JS\"},{\"text\":\"CSS\",\"link\":\"/Java学前端/CSS\"},{\"text\":\"Vue2+组件\",\"link\":\"/Java学前端/Vue2+组件\"},{\"text\":\"Vue3+组件\",\"link\":\"/Java学前端/Vue3+组件\"},{\"text\":\"React\",\"link\":\"/Java学前端/React\"}]}],\"activeMatch\":\"/Java/\"},{\"text\":\"软件测试\",\"items\":[{\"items\":[{\"text\":\"测试基础\",\"link\":\"/软件测试/测试基础\"},{\"text\":\"压力测试\",\"link\":\"/软件测试/压力测试\"}]}]},{\"text\":\"多线程\",\"items\":[{\"items\":[{\"text\":\"基础篇\",\"link\":\"/并发 & 多线程/基础篇\"},{\"text\":\"进阶篇\",\"link\":\"/并发 & 多线程/并发完善\"}]}]},{\"text\":\"开发工具\",\"items\":[{\"items\":[{\"text\":\"Chrome\",\"link\":\"/IDEA/Chrome\"},{\"text\":\"IDEA基础\",\"link\":\"/IDEA/IDEA基础\"},{\"text\":\"IDEA插件\",\"link\":\"/IDEA/IDEA插件\"},{\"text\":\"VS Code\",\"link\":\"/IDEA/VS Code\"}]}]},{\"text\":\"消息中间件\",\"items\":[{\"items\":[{\"text\":\"RabbitMQ\",\"link\":\"/消息中间件/RabbitMQ\"},{\"text\":\"RocketMQ\",\"link\":\"/消息中间件/RocketMQ\"},{\"text\":\"Kafka\",\"link\":\"/消息中间件/Kafka\"},{\"text\":\"Canal\",\"link\":\"/消息中间件/Canal\"}]}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/renshuo123/renshuo123.github.io\"},{\"icon\":\"twitter\",\"link\":\"#\"},{\"icon\":{\"svg\":\"<svg t=\\\"1676028692954\\\" class=\\\"icon\\\" ...</path></svg>\"},\"link\":\"https://github.com/\"}]},\"locales\":{},\"scrollOffset\":90,\"cleanUrls\":false}");</script>
    
  </body>
</html>